1use crate::{
37 constants::{DEFAULT_GAS_LIMIT, DEFAULT_TRANSACTION_SPEED},
38 models::{
39 evm::Speed, EvmNetwork, EvmTransactionData, EvmTransactionDataTrait, RelayerRepoModel,
40 TransactionError, U256,
41 },
42 services::{
43 evm_gas_price::{EvmGasPriceServiceTrait, GasPrices},
44 gas::price_params_handler::PriceParamsHandler,
45 },
46};
47
48#[cfg(test)]
49use mockall::automock;
50
51#[async_trait::async_trait]
52#[cfg_attr(test, automock)]
53pub trait PriceCalculatorTrait: Send + Sync {
54 async fn get_transaction_price_params(
55 &self,
56 tx_data: &EvmTransactionData,
57 relayer: &RelayerRepoModel,
58 ) -> Result<PriceParams, TransactionError>;
59
60 async fn calculate_bumped_gas_price(
61 &self,
62 tx_data: &EvmTransactionData,
63 relayer: &RelayerRepoModel,
64 force_bump: bool,
65 ) -> Result<PriceParams, TransactionError>;
66}
67
68const PRECISION: u128 = 1_000_000_000; const MINUTE_AND_HALF_MS: u128 = 90000;
70const BASE_FEE_INCREASE_RATE: f64 = 1.125; const MAX_BASE_FEE_MULTIPLIER: u128 = 10 * PRECISION; #[derive(Debug, Clone)]
74pub struct PriceParams {
75 pub gas_price: Option<u128>,
76 pub max_fee_per_gas: Option<u128>,
77 pub max_priority_fee_per_gas: Option<u128>,
78 pub is_min_bumped: Option<bool>,
79 pub extra_fee: Option<U256>,
80 pub total_cost: U256,
81}
82
83impl PriceParams {
84 pub fn calculate_total_cost(&self, is_eip1559: bool, gas_limit: u64, value: U256) -> U256 {
85 match is_eip1559 {
86 true => {
87 U256::from(self.max_fee_per_gas.unwrap_or(0)) * U256::from(gas_limit)
88 + value
89 + self.extra_fee.unwrap_or(U256::ZERO)
90 }
91 false => {
92 U256::from(self.gas_price.unwrap_or(0)) * U256::from(gas_limit)
93 + value
94 + self.extra_fee.unwrap_or(U256::ZERO)
95 }
96 }
97 }
98}
99
100pub fn calculate_min_bump(base_price: u128) -> u128 {
111 const BUMP_NUMERATOR: u128 = 11;
114 const BUMP_DENOMINATOR: u128 = 10;
115
116 let bumped_price = base_price
117 .saturating_mul(BUMP_NUMERATOR)
118 .saturating_div(BUMP_DENOMINATOR);
119
120 std::cmp::max(bumped_price, base_price.saturating_add(1))
122}
123
124pub struct PriceCalculator<G: EvmGasPriceServiceTrait> {
126 gas_price_service: G,
127 price_params_handler: Option<PriceParamsHandler>,
128}
129
130#[async_trait::async_trait]
131impl<G> PriceCalculatorTrait for PriceCalculator<G>
132where
133 G: EvmGasPriceServiceTrait + Send + Sync,
134{
135 async fn get_transaction_price_params(
136 &self,
137 tx_data: &EvmTransactionData,
138 relayer: &RelayerRepoModel,
139 ) -> Result<PriceParams, TransactionError> {
140 PriceCalculator::<G>::get_transaction_price_params(self, tx_data, relayer).await
141 }
142
143 async fn calculate_bumped_gas_price(
144 &self,
145 tx_data: &EvmTransactionData,
146 relayer: &RelayerRepoModel,
147 force_bump: bool,
148 ) -> Result<PriceParams, TransactionError> {
149 PriceCalculator::<G>::calculate_bumped_gas_price(self, tx_data, relayer, force_bump).await
150 }
151}
152
153impl<G> PriceCalculator<G>
154where
155 G: EvmGasPriceServiceTrait,
156{
157 pub fn new(gas_price_service: G, price_params_handler: Option<PriceParamsHandler>) -> Self {
158 Self {
159 gas_price_service,
160 price_params_handler,
161 }
162 }
163
164 pub async fn get_transaction_price_params(
180 &self,
181 tx_data: &EvmTransactionData,
182 relayer: &RelayerRepoModel,
183 ) -> Result<PriceParams, TransactionError> {
184 let mut price_final_params = self
185 .fetch_price_params_based_on_tx_type(tx_data, relayer)
186 .await?;
187
188 self.apply_gas_price_cap_and_constraints(&mut price_final_params, relayer)?;
190
191 self.finalize_price_params(relayer, tx_data, price_final_params)
193 .await
194 }
195
196 pub async fn calculate_bumped_gas_price(
216 &self,
217 tx_data: &EvmTransactionData,
218 relayer: &RelayerRepoModel,
219 force_bump: bool,
220 ) -> Result<PriceParams, TransactionError> {
221 if self.gas_price_service.network().lacks_mempool()
223 || self.gas_price_service.network().is_arbitrum()
224 {
225 let mut price_params = self.get_transaction_price_params(tx_data, relayer).await?;
226
227 price_params.is_min_bumped = Some(true);
229 return Ok(price_params);
230 }
231
232 let network_gas_prices = self.gas_price_service.get_prices_from_json_rpc().await?;
233
234 let relayer_gas_price_cap = if force_bump {
236 u128::MAX
237 } else {
238 relayer
239 .policies
240 .get_evm_policy()
241 .gas_price_cap
242 .unwrap_or(u128::MAX)
243 };
244
245 let bumped_price_params = match (
247 tx_data.max_fee_per_gas,
248 tx_data.max_priority_fee_per_gas,
249 tx_data.gas_price,
250 ) {
251 (Some(max_fee), Some(max_priority_fee), _) => {
252 self.handle_eip1559_bump(
254 &network_gas_prices,
255 relayer_gas_price_cap,
256 tx_data.speed.as_ref(),
257 max_fee,
258 max_priority_fee,
259 )?
260 }
261 (None, None, Some(gas_price)) => {
262 self.handle_legacy_bump(
264 &network_gas_prices,
265 relayer_gas_price_cap,
266 tx_data.speed.as_ref(),
267 gas_price,
268 )?
269 }
270 _ => {
271 return Err(TransactionError::InvalidType(
272 "Transaction missing required gas price parameters".to_string(),
273 ))
274 }
275 };
276
277 self.finalize_price_params(relayer, tx_data, bumped_price_params)
279 .await
280 }
281
282 fn handle_eip1559_bump(
298 &self,
299 network_gas_prices: &GasPrices,
300 gas_price_cap: u128,
301 maybe_speed: Option<&Speed>,
302 max_fee: u128,
303 max_priority_fee: u128,
304 ) -> Result<PriceParams, TransactionError> {
305 let speed = maybe_speed.unwrap_or(&DEFAULT_TRANSACTION_SPEED);
306
307 let min_bump_max_fee = calculate_min_bump(max_fee);
309 let min_bump_max_priority = calculate_min_bump(max_priority_fee);
310
311 let current_market_priority =
313 Self::get_market_price_for_speed(network_gas_prices, true, speed);
314
315 let bumped_priority_fee = if current_market_priority >= min_bump_max_priority {
319 current_market_priority
320 } else {
321 min_bump_max_priority
322 };
323
324 let base_fee_wei = network_gas_prices.base_fee_per_gas;
327 let bumped_max_fee_per_gas = if base_fee_wei >= min_bump_max_fee {
328 base_fee_wei
329 } else {
330 min_bump_max_fee
331 };
332
333 let recommended_max_fee_per_gas = calculate_max_fee_per_gas(
336 base_fee_wei,
337 bumped_priority_fee,
338 self.gas_price_service.network(),
339 );
340
341 let final_max_fee = std::cmp::max(bumped_max_fee_per_gas, recommended_max_fee_per_gas);
343
344 let capped_priority = Self::cap_gas_price(bumped_priority_fee, gas_price_cap);
346 let capped_max_fee = Self::cap_gas_price(final_max_fee, gas_price_cap);
347
348 let is_min_bumped =
350 capped_priority >= min_bump_max_priority && capped_max_fee >= min_bump_max_fee;
351
352 Ok(PriceParams {
354 gas_price: None,
355 max_priority_fee_per_gas: Some(capped_priority),
356 max_fee_per_gas: Some(capped_max_fee),
357 is_min_bumped: Some(is_min_bumped),
358 extra_fee: None,
359 total_cost: U256::ZERO,
360 })
361 }
362
363 fn handle_legacy_bump(
368 &self,
369 network_gas_prices: &GasPrices,
370 gas_price_cap: u128,
371 maybe_speed: Option<&Speed>,
372 gas_price: u128,
373 ) -> Result<PriceParams, TransactionError> {
374 let speed = maybe_speed.unwrap_or(&Speed::Fast);
375
376 let min_bump_gas_price = calculate_min_bump(gas_price);
378
379 let current_market_price =
381 Self::get_market_price_for_speed(network_gas_prices, false, speed);
382
383 let bumped_gas_price = if current_market_price >= min_bump_gas_price {
384 current_market_price
385 } else {
386 min_bump_gas_price
387 };
388
389 let capped_gas_price = Self::cap_gas_price(bumped_gas_price, gas_price_cap);
391
392 let is_min_bumped = capped_gas_price >= min_bump_gas_price;
394
395 Ok(PriceParams {
396 gas_price: Some(capped_gas_price),
397 max_priority_fee_per_gas: None,
398 max_fee_per_gas: None,
399 is_min_bumped: Some(is_min_bumped),
400 extra_fee: None,
401 total_cost: U256::ZERO,
402 })
403 }
404 async fn fetch_price_params_based_on_tx_type(
406 &self,
407 tx_data: &EvmTransactionData,
408 relayer: &RelayerRepoModel,
409 ) -> Result<PriceParams, TransactionError> {
410 if tx_data.is_legacy() {
411 self.fetch_legacy_price_params(tx_data)
412 } else if tx_data.is_eip1559() {
413 self.fetch_eip1559_price_params(tx_data)
414 } else if tx_data.is_speed() {
415 self.fetch_speed_price_params(tx_data, relayer).await
416 } else {
417 Err(TransactionError::NotSupported(
418 "Invalid transaction type".to_string(),
419 ))
420 }
421 }
422
423 fn fetch_legacy_price_params(
431 &self,
432 tx_data: &EvmTransactionData,
433 ) -> Result<PriceParams, TransactionError> {
434 let gas_price = tx_data.gas_price.ok_or(TransactionError::NotSupported(
435 "Gas price is required for legacy transactions".to_string(),
436 ))?;
437 Ok(PriceParams {
438 gas_price: Some(gas_price),
439 max_fee_per_gas: None,
440 max_priority_fee_per_gas: None,
441 is_min_bumped: None,
442 extra_fee: None,
443 total_cost: U256::ZERO,
444 })
445 }
446
447 fn fetch_eip1559_price_params(
448 &self,
449 tx_data: &EvmTransactionData,
450 ) -> Result<PriceParams, TransactionError> {
451 let max_fee = tx_data
452 .max_fee_per_gas
453 .ok_or(TransactionError::NotSupported(
454 "Max fee per gas is required for EIP1559 transactions".to_string(),
455 ))?;
456 let max_priority_fee =
457 tx_data
458 .max_priority_fee_per_gas
459 .ok_or(TransactionError::NotSupported(
460 "Max priority fee per gas is required for EIP1559 transactions".to_string(),
461 ))?;
462 Ok(PriceParams {
463 gas_price: None,
464 max_fee_per_gas: Some(max_fee),
465 max_priority_fee_per_gas: Some(max_priority_fee),
466 is_min_bumped: None,
467 extra_fee: None,
468 total_cost: U256::ZERO,
469 })
470 }
471 async fn fetch_speed_price_params(
476 &self,
477 tx_data: &EvmTransactionData,
478 relayer: &RelayerRepoModel,
479 ) -> Result<PriceParams, TransactionError> {
480 let speed = tx_data
481 .speed
482 .as_ref()
483 .ok_or(TransactionError::NotSupported(
484 "Speed is required".to_string(),
485 ))?;
486 let use_legacy = relayer.policies.get_evm_policy().eip1559_pricing == Some(false)
487 || self.gas_price_service.network().is_legacy();
488
489 if use_legacy {
490 self.fetch_legacy_speed_params(speed).await
491 } else {
492 self.fetch_eip1559_speed_params(speed).await
493 }
494 }
495
496 async fn fetch_eip1559_speed_params(
501 &self,
502 speed: &Speed,
503 ) -> Result<PriceParams, TransactionError> {
504 let prices = self.gas_price_service.get_prices_from_json_rpc().await?;
505 let priority_fee = match speed {
506 Speed::SafeLow => prices.max_priority_fee_per_gas.safe_low,
507 Speed::Average => prices.max_priority_fee_per_gas.average,
508 Speed::Fast => prices.max_priority_fee_per_gas.fast,
509 Speed::Fastest => prices.max_priority_fee_per_gas.fastest,
510 };
511 let max_fee = calculate_max_fee_per_gas(
512 prices.base_fee_per_gas,
513 priority_fee,
514 self.gas_price_service.network(),
515 );
516 Ok(PriceParams {
517 gas_price: None,
518 max_fee_per_gas: Some(max_fee),
519 max_priority_fee_per_gas: Some(priority_fee),
520 is_min_bumped: None,
521 extra_fee: None,
522 total_cost: U256::ZERO,
523 })
524 }
525 async fn fetch_legacy_speed_params(
530 &self,
531 speed: &Speed,
532 ) -> Result<PriceParams, TransactionError> {
533 let prices = self
534 .gas_price_service
535 .get_legacy_prices_from_json_rpc()
536 .await?;
537 let gas_price = match speed {
538 Speed::SafeLow => prices.safe_low,
539 Speed::Average => prices.average,
540 Speed::Fast => prices.fast,
541 Speed::Fastest => prices.fastest,
542 };
543 Ok(PriceParams {
544 gas_price: Some(gas_price),
545 max_fee_per_gas: None,
546 max_priority_fee_per_gas: None,
547 is_min_bumped: None,
548 extra_fee: None,
549 total_cost: U256::ZERO,
550 })
551 }
552
553 fn apply_gas_price_cap_and_constraints(
559 &self,
560 price_params: &mut PriceParams,
561 relayer: &RelayerRepoModel,
562 ) -> Result<(), TransactionError> {
563 let gas_price_cap = relayer
564 .policies
565 .get_evm_policy()
566 .gas_price_cap
567 .unwrap_or(u128::MAX);
568
569 if let (Some(max_fee), Some(max_priority)) = (
570 price_params.max_fee_per_gas,
571 price_params.max_priority_fee_per_gas,
572 ) {
573 let capped_max_fee = Self::cap_gas_price(max_fee, gas_price_cap);
575 price_params.max_fee_per_gas = Some(capped_max_fee);
576
577 price_params.max_priority_fee_per_gas =
579 Some(Self::cap_gas_price(max_priority, capped_max_fee));
580
581 price_params.gas_price = None;
583 } else {
584 price_params.gas_price = Some(Self::cap_gas_price(
586 price_params.gas_price.unwrap_or_default(),
587 gas_price_cap,
588 ));
589
590 price_params.max_fee_per_gas = None;
592 price_params.max_priority_fee_per_gas = None;
593 }
594
595 Ok(())
596 }
597
598 fn cap_gas_price(price: u128, cap: u128) -> u128 {
599 std::cmp::min(price, cap)
600 }
601
602 async fn finalize_price_params(
604 &self,
605 relayer: &RelayerRepoModel,
606 tx_data: &EvmTransactionData,
607 mut price_params: PriceParams,
608 ) -> Result<PriceParams, TransactionError> {
609 let is_eip1559 = tx_data.is_eip1559();
610
611 if let Some(handler) = &self.price_params_handler {
613 price_params = handler.handle_price_params(tx_data, price_params).await?;
614
615 self.apply_gas_price_cap_and_constraints(&mut price_params, relayer)?;
617 }
618
619 if price_params.total_cost == U256::ZERO {
620 price_params.total_cost = price_params.calculate_total_cost(
621 is_eip1559,
622 tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
623 U256::from(tx_data.value),
624 );
625 }
626
627 Ok(price_params)
628 }
629
630 fn get_market_price_for_speed(prices: &GasPrices, is_eip1559: bool, speed: &Speed) -> u128 {
633 if is_eip1559 {
634 match speed {
635 Speed::SafeLow => prices.max_priority_fee_per_gas.safe_low,
636 Speed::Average => prices.max_priority_fee_per_gas.average,
637 Speed::Fast => prices.max_priority_fee_per_gas.fast,
638 Speed::Fastest => prices.max_priority_fee_per_gas.fastest,
639 }
640 } else {
641 match speed {
642 Speed::SafeLow => prices.legacy_prices.safe_low,
643 Speed::Average => prices.legacy_prices.average,
644 Speed::Fast => prices.legacy_prices.fast,
645 Speed::Fastest => prices.legacy_prices.fastest,
646 }
647 }
648 }
649}
650
651fn get_base_fee_multiplier(network: &EvmNetwork) -> u128 {
652 let block_interval_ms = network.average_blocktime().map(|d| d.as_millis()).unwrap();
653
654 let n_blocks = MINUTE_AND_HALF_MS / block_interval_ms;
656
657 let multiplier_f64 = BASE_FEE_INCREASE_RATE.powi(n_blocks as i32);
659
660 let multiplier = (multiplier_f64 * PRECISION as f64) as u128;
662
663 std::cmp::min(multiplier, MAX_BASE_FEE_MULTIPLIER)
665}
666
667fn calculate_max_fee_per_gas(
669 base_fee_wei: u128,
670 max_priority_fee_wei: u128,
671 network: &EvmNetwork,
672) -> u128 {
673 let multiplier = get_base_fee_multiplier(network);
675
676 let multiplied_base_fee = (base_fee_wei * multiplier) / PRECISION;
678
679 multiplied_base_fee + max_priority_fee_wei
681}
682#[cfg(test)]
683mod tests {
684 use super::*;
685 use crate::constants::{ARBITRUM_BASED_TAG, NO_MEMPOOL_TAG};
686 use crate::models::{
687 evm::Speed, EvmNetwork, EvmTransactionData, NetworkType, RelayerEvmPolicy,
688 RelayerNetworkPolicy, RelayerRepoModel, U256,
689 };
690 use crate::services::{
691 evm_gas_price::{EvmGasPriceService, GasPrices, MockEvmGasPriceServiceTrait, SpeedPrices},
692 gas::handlers::test_mock::MockPriceHandler,
693 provider::MockEvmProviderTrait,
694 };
695 use futures::FutureExt;
696
697 fn create_mock_evm_network(name: &str) -> EvmNetwork {
698 let average_blocktime_ms = match name {
699 "optimism" => 2000, _ => 12000, };
702
703 use crate::models::RpcConfig;
704 EvmNetwork {
705 network: name.to_string(),
706 rpc_urls: vec![RpcConfig::new("https://rpc.example.com".to_string())],
707 explorer_urls: None,
708 average_blocktime_ms,
709 is_testnet: true,
710 tags: vec![],
711 chain_id: 1337,
712 required_confirmations: 1,
713 features: vec![],
714 symbol: "ETH".to_string(),
715 gas_price_cache: None,
716 }
717 }
718
719 fn create_mock_no_mempool_network(name: &str) -> EvmNetwork {
720 use crate::models::RpcConfig;
721 let average_blocktime_ms = match name {
722 "arbitrum" => 1000, _ => 12000, };
725
726 EvmNetwork {
727 network: name.to_string(),
728 rpc_urls: vec![RpcConfig::new("https://rpc.example.com".to_string())],
729 explorer_urls: None,
730 average_blocktime_ms,
731 is_testnet: true,
732 tags: vec![NO_MEMPOOL_TAG.to_string()], chain_id: 42161,
734 required_confirmations: 1,
735 features: vec!["eip1559".to_string()], symbol: "ETH".to_string(),
737 gas_price_cache: None,
738 }
739 }
740
741 fn create_mock_relayer() -> RelayerRepoModel {
742 RelayerRepoModel {
743 id: "test-relayer".to_string(),
744 name: "Test Relayer".to_string(),
745 network: "mainnet".to_string(),
746 network_type: NetworkType::Evm,
747 address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string(),
748 policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy::default()),
749 paused: false,
750 notification_id: None,
751 signer_id: "test-signer".to_string(),
752 system_disabled: false,
753 custom_rpc_urls: None,
754 ..Default::default()
755 }
756 }
757
758 #[tokio::test]
759 async fn test_legacy_transaction() {
760 let mut provider = MockEvmProviderTrait::new();
761 provider
762 .expect_get_balance()
763 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
764
765 let relayer = create_mock_relayer();
766 let gas_price_service =
767 EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"), None);
768
769 let tx_data = EvmTransactionData {
770 gas_price: Some(20000000000),
771 ..Default::default()
772 };
773
774 let mut provider = MockEvmProviderTrait::new();
775 provider
776 .expect_get_balance()
777 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
778
779 let pc = PriceCalculator::new(gas_price_service, None);
781
782 let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
783 assert!(result.is_ok());
784 let params = result.unwrap();
785 assert_eq!(params.gas_price, Some(20000000000));
786 assert!(params.max_fee_per_gas.is_none());
787 assert!(params.max_priority_fee_per_gas.is_none());
788 }
789
790 #[tokio::test]
791 async fn test_eip1559_transaction() {
792 let mut provider = MockEvmProviderTrait::new();
793 provider
794 .expect_get_balance()
795 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
796
797 let relayer = create_mock_relayer();
798 let gas_price_service =
799 EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"), None);
800
801 let tx_data = EvmTransactionData {
802 gas_price: None,
803 max_fee_per_gas: Some(30000000000),
804 max_priority_fee_per_gas: Some(2000000000),
805 ..Default::default()
806 };
807
808 let mut provider = MockEvmProviderTrait::new();
809 provider
810 .expect_get_balance()
811 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
812
813 let pc = PriceCalculator::new(gas_price_service, None);
815
816 let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
817 assert!(result.is_ok());
818 let params = result.unwrap();
819 assert!(params.gas_price.is_none());
820 assert_eq!(params.max_fee_per_gas, Some(30000000000));
821 assert_eq!(params.max_priority_fee_per_gas, Some(2000000000));
822 }
823
824 #[tokio::test]
825 async fn test_speed_legacy_based_transaction() {
826 let mut provider = MockEvmProviderTrait::new();
827 provider
828 .expect_get_balance()
829 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
830 provider
831 .expect_get_gas_price()
832 .returning(|| async { Ok(20000000000) }.boxed());
833
834 let relayer = create_mock_relayer();
835 let gas_price_service =
836 EvmGasPriceService::new(provider, create_mock_evm_network("celo"), None);
837
838 let tx_data = EvmTransactionData {
839 gas_price: None,
840 speed: Some(Speed::Fast),
841 ..Default::default()
842 };
843
844 let mut provider = MockEvmProviderTrait::new();
845 provider
846 .expect_get_balance()
847 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
848 provider
849 .expect_get_gas_price()
850 .returning(|| async { Ok(20000000000) }.boxed());
851
852 let pc = PriceCalculator::new(gas_price_service, None);
853
854 let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
855 assert!(result.is_ok());
856 let params = result.unwrap();
857 assert!(
858 params.gas_price.is_some()
859 || (params.max_fee_per_gas.is_some() && params.max_priority_fee_per_gas.is_some())
860 );
861 }
862
863 #[tokio::test]
864 async fn test_invalid_transaction_type() {
865 let mut provider = MockEvmProviderTrait::new();
866 provider
867 .expect_get_balance()
868 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
869
870 let relayer = create_mock_relayer();
871 let gas_price_service =
872 EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"), None);
873
874 let tx_data = EvmTransactionData {
875 gas_price: None,
876 ..Default::default()
877 };
878
879 let mut provider = MockEvmProviderTrait::new();
880 provider
881 .expect_get_balance()
882 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
883
884 let pc = PriceCalculator::new(gas_price_service, None);
885
886 let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
887 assert!(result.is_err());
888 assert!(matches!(
889 result.unwrap_err(),
890 TransactionError::NotSupported(_)
891 ));
892 }
893
894 #[tokio::test]
895 async fn test_gas_price_cap() {
896 let mut provider = MockEvmProviderTrait::new();
897 provider
898 .expect_get_balance()
899 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
900
901 let mut relayer = create_mock_relayer();
902 let gas_price_service =
903 EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"), None);
904
905 let evm_policy = RelayerEvmPolicy {
907 gas_price_cap: Some(10000000000),
908 eip1559_pricing: Some(true),
909 ..RelayerEvmPolicy::default()
910 };
911 relayer.policies = RelayerNetworkPolicy::Evm(evm_policy);
912
913 let tx_data = EvmTransactionData {
914 gas_price: Some(20000000000), ..Default::default()
916 };
917
918 let mut provider = MockEvmProviderTrait::new();
919 provider
920 .expect_get_balance()
921 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
922
923 let pc = PriceCalculator::new(gas_price_service, None);
924
925 let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
926 assert!(result.is_ok());
927 let params = result.unwrap();
928 assert_eq!(params.gas_price, Some(10000000000)); }
930
931 #[test]
932 fn test_get_base_fee_multiplier() {
933 let mainnet = create_mock_evm_network("mainnet");
934 let multiplier = super::get_base_fee_multiplier(&mainnet);
935 assert!(multiplier > 2_200_000_000 && multiplier < 2_400_000_000);
937
938 let optimism = create_mock_evm_network("optimism");
939 let multiplier = super::get_base_fee_multiplier(&optimism);
940 assert_eq!(multiplier, MAX_BASE_FEE_MULTIPLIER);
942 }
943
944 #[test]
945 fn test_get_base_fee_multiplier_overflow_protection() {
946 let mut test_network = create_mock_evm_network("test");
948
949 test_network.average_blocktime_ms = 1;
951 let multiplier = super::get_base_fee_multiplier(&test_network);
952 assert_eq!(multiplier, MAX_BASE_FEE_MULTIPLIER);
954
955 test_network.average_blocktime_ms = 100;
957 let multiplier = super::get_base_fee_multiplier(&test_network);
958 assert_eq!(multiplier, MAX_BASE_FEE_MULTIPLIER);
960
961 test_network.average_blocktime_ms = 1000;
963 let multiplier = super::get_base_fee_multiplier(&test_network);
964 assert_eq!(multiplier, MAX_BASE_FEE_MULTIPLIER);
966
967 test_network.average_blocktime_ms = 5000;
969 let multiplier = super::get_base_fee_multiplier(&test_network);
970 assert!(multiplier > 8_000_000_000 && multiplier < 9_000_000_000);
972 assert!(multiplier < MAX_BASE_FEE_MULTIPLIER);
973
974 test_network.average_blocktime_ms = 10000;
976 let multiplier = super::get_base_fee_multiplier(&test_network);
977 assert!(multiplier > 2_500_000_000 && multiplier < 3_000_000_000);
979 }
980
981 #[test]
982 fn test_calculate_max_fee_per_gas() {
983 let network = create_mock_evm_network("mainnet");
984 let base_fee = 100_000_000_000u128; let priority_fee = 2_000_000_000u128; let max_fee = super::calculate_max_fee_per_gas(base_fee, priority_fee, &network);
988 assert!(max_fee > 225_000_000_000 && max_fee < 235_000_000_000);
991 }
992
993 #[tokio::test]
994 async fn test_handle_eip1559_speed() {
995 let mut mock_gas_price_service = MockEvmGasPriceServiceTrait::new();
996
997 let test_data = [
999 (Speed::SafeLow, 1_000_000_000),
1000 (Speed::Average, 2_000_000_000),
1001 (Speed::Fast, 3_000_000_000),
1002 (Speed::Fastest, 4_000_000_000),
1003 ];
1004 let mock_prices = GasPrices {
1006 legacy_prices: SpeedPrices {
1007 safe_low: 10_000_000_000,
1008 average: 12_500_000_000,
1009 fast: 15_000_000_000,
1010 fastest: 20_000_000_000,
1011 },
1012 max_priority_fee_per_gas: SpeedPrices {
1013 safe_low: 1_000_000_000,
1014 average: 2_000_000_000,
1015 fast: 3_000_000_000,
1016 fastest: 4_000_000_000,
1017 },
1018 base_fee_per_gas: 50_000_000_000,
1019 };
1020
1021 mock_gas_price_service
1023 .expect_get_prices_from_json_rpc()
1024 .returning(move || {
1025 let prices = mock_prices.clone();
1026 Box::pin(async move { Ok(prices) })
1027 });
1028
1029 let network = create_mock_evm_network("mainnet");
1031 mock_gas_price_service
1032 .expect_network()
1033 .return_const(network);
1034
1035 let pc = PriceCalculator::new(mock_gas_price_service, None);
1037
1038 for (speed, expected_priority_fee) in test_data {
1039 let result = pc.fetch_eip1559_speed_params(&speed).await;
1041 assert!(result.is_ok());
1042 let params = result.unwrap();
1043 assert_eq!(params.max_priority_fee_per_gas, Some(expected_priority_fee));
1045
1046 let max_fee = params.max_fee_per_gas.unwrap();
1050 let expected_base_portion = 120_000_000_000; assert!(max_fee < expected_base_portion + expected_priority_fee + 2_000_000_000);
1052 }
1053 }
1054
1055 #[tokio::test]
1056 async fn test_calculate_bumped_gas_price_eip1559_basic() {
1057 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1058 let mock_prices = GasPrices {
1059 legacy_prices: SpeedPrices {
1060 safe_low: 8_000_000_000,
1061 average: 10_000_000_000,
1062 fast: 12_000_000_000,
1063 fastest: 15_000_000_000,
1064 },
1065 max_priority_fee_per_gas: SpeedPrices {
1066 safe_low: 1_000_000_000,
1067 average: 2_000_000_000,
1068 fast: 3_000_000_000,
1069 fastest: 4_000_000_000,
1070 },
1071 base_fee_per_gas: 50_000_000_000,
1072 };
1073 mock_service
1074 .expect_get_prices_from_json_rpc()
1075 .returning(move || {
1076 let prices = mock_prices.clone();
1077 Box::pin(async move { Ok(prices) })
1078 });
1079 mock_service
1080 .expect_network()
1081 .return_const(create_mock_evm_network("mainnet"));
1082
1083 let pc = PriceCalculator::new(mock_service, None);
1084 let mut relayer = create_mock_relayer();
1085 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1087 gas_price_cap: Some(300_000_000_000u128),
1088 ..Default::default()
1089 });
1090
1091 let tx_data = EvmTransactionData {
1092 max_fee_per_gas: Some(100_000_000_000),
1093 max_priority_fee_per_gas: Some(2_000_000_000),
1094 speed: Some(Speed::Fast),
1095 ..Default::default()
1096 };
1097
1098 let bumped = pc
1099 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1100 .await
1101 .unwrap();
1102 assert!(bumped.max_fee_per_gas.unwrap() >= 110_000_000_000); assert!(bumped.max_priority_fee_per_gas.unwrap() >= 2_200_000_000); }
1105
1106 #[tokio::test]
1107 async fn test_calculate_bumped_gas_price_eip1559_market_lower_than_min_bump() {
1108 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1109 let mock_prices = GasPrices {
1110 legacy_prices: SpeedPrices::default(),
1111 max_priority_fee_per_gas: SpeedPrices {
1112 safe_low: 1_500_000_000, average: 2_500_000_000,
1114 fast: 2_700_000_000,
1115 fastest: 3_000_000_000,
1116 },
1117 base_fee_per_gas: 30_000_000_000,
1118 };
1119 mock_service
1120 .expect_get_prices_from_json_rpc()
1121 .returning(move || {
1122 let prices = mock_prices.clone();
1123 Box::pin(async move { Ok(prices) })
1124 });
1125 mock_service
1126 .expect_network()
1127 .return_const(create_mock_evm_network("mainnet"));
1128
1129 let pc = PriceCalculator::new(mock_service, None);
1130 let relayer = create_mock_relayer();
1131
1132 let tx_data = EvmTransactionData {
1135 max_fee_per_gas: Some(20_000_000_000),
1136 max_priority_fee_per_gas: Some(2_000_000_000),
1137 speed: Some(Speed::SafeLow),
1138 ..Default::default()
1139 };
1140
1141 let bumped = pc
1142 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1143 .await
1144 .unwrap();
1145 assert!(bumped.max_priority_fee_per_gas.unwrap() >= 2_200_000_000);
1146 assert!(bumped.max_fee_per_gas.unwrap() > 20_000_000_000);
1147 }
1148
1149 #[tokio::test]
1150 async fn test_calculate_bumped_gas_price_legacy_basic() {
1151 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1152 let mock_prices = GasPrices {
1153 legacy_prices: SpeedPrices {
1154 safe_low: 10_000_000_000,
1155 average: 12_000_000_000,
1156 fast: 14_000_000_000,
1157 fastest: 18_000_000_000,
1158 },
1159 max_priority_fee_per_gas: SpeedPrices::default(),
1160 base_fee_per_gas: 0,
1161 };
1162 mock_service
1163 .expect_get_prices_from_json_rpc()
1164 .returning(move || {
1165 let prices = mock_prices.clone();
1166 Box::pin(async move { Ok(prices) })
1167 });
1168 mock_service
1169 .expect_network()
1170 .return_const(create_mock_evm_network("mainnet"));
1171
1172 let pc = PriceCalculator::new(mock_service, None);
1173 let relayer = create_mock_relayer();
1174 let tx_data = EvmTransactionData {
1175 gas_price: Some(10_000_000_000),
1176 speed: Some(Speed::Fast),
1177 ..Default::default()
1178 };
1179
1180 let bumped = pc
1181 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1182 .await
1183 .unwrap();
1184 assert!(bumped.gas_price.unwrap() >= 11_000_000_000); }
1186
1187 #[tokio::test]
1188 async fn test_calculate_bumped_gas_price_missing_params() {
1189 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1190
1191 mock_service
1193 .expect_get_prices_from_json_rpc()
1194 .times(1)
1195 .returning(|| Box::pin(async { Ok(GasPrices::default()) }));
1196
1197 mock_service
1199 .expect_network()
1200 .return_const(create_mock_evm_network("mainnet"));
1201
1202 let pc = PriceCalculator::new(mock_service, None);
1203 let relayer = create_mock_relayer();
1204 let tx_data = EvmTransactionData {
1206 gas_price: None,
1207 max_fee_per_gas: None,
1208 max_priority_fee_per_gas: None,
1209 ..Default::default()
1210 };
1211
1212 let result = pc
1213 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1214 .await;
1215 assert!(result.is_err());
1216 if let Err(TransactionError::InvalidType(msg)) = result {
1217 assert!(msg.contains("missing required gas price parameters"));
1218 } else {
1219 panic!("Expected InvalidType error");
1220 }
1221 }
1222
1223 #[tokio::test]
1224 async fn test_calculate_bumped_gas_price_capped() {
1225 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1226 let mock_prices = GasPrices {
1227 legacy_prices: SpeedPrices::default(),
1228 max_priority_fee_per_gas: SpeedPrices {
1229 safe_low: 4_000_000_000,
1230 average: 5_000_000_000,
1231 fast: 6_000_000_000,
1232 fastest: 8_000_000_000,
1233 },
1234 base_fee_per_gas: 100_000_000_000,
1235 };
1236 mock_service
1237 .expect_get_prices_from_json_rpc()
1238 .returning(move || {
1239 let prices = mock_prices.clone();
1240 Box::pin(async move { Ok(prices) })
1241 });
1242 mock_service
1243 .expect_network()
1244 .return_const(create_mock_evm_network("mainnet"));
1245
1246 let pc = PriceCalculator::new(mock_service, None);
1247 let mut relayer = create_mock_relayer();
1248 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1249 gas_price_cap: Some(105_000_000_000),
1250 ..Default::default()
1251 });
1252
1253 let tx_data = EvmTransactionData {
1254 max_fee_per_gas: Some(90_000_000_000),
1255 max_priority_fee_per_gas: Some(4_000_000_000),
1256 speed: Some(Speed::Fastest),
1257 ..Default::default()
1258 };
1259
1260 let bumped = pc
1262 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1263 .await
1264 .unwrap();
1265 assert!(bumped.max_fee_per_gas.unwrap() <= 105_000_000_000);
1266 assert!(bumped.max_priority_fee_per_gas.unwrap() <= 105_000_000_000);
1267 }
1268
1269 #[tokio::test]
1270 async fn test_is_min_bumped_flag_eip1559() {
1271 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1272 let mock_prices = GasPrices {
1273 legacy_prices: SpeedPrices::default(),
1274 max_priority_fee_per_gas: SpeedPrices {
1275 safe_low: 1_000_000_000,
1276 average: 2_000_000_000,
1277 fast: 3_000_000_000,
1278 fastest: 4_000_000_000,
1279 },
1280 base_fee_per_gas: 40_000_000_000,
1281 };
1282 mock_service
1283 .expect_get_prices_from_json_rpc()
1284 .returning(move || {
1285 let prices = mock_prices.clone();
1286 Box::pin(async move { Ok(prices) })
1287 });
1288 mock_service
1289 .expect_network()
1290 .return_const(create_mock_evm_network("mainnet"));
1291
1292 let pc = PriceCalculator::new(mock_service, None);
1293 let mut relayer = create_mock_relayer();
1294
1295 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1297 gas_price_cap: Some(200_000_000_000u128),
1298 ..Default::default()
1299 });
1300
1301 let tx_data = EvmTransactionData {
1302 max_fee_per_gas: Some(50_000_000_000),
1303 max_priority_fee_per_gas: Some(2_000_000_000),
1304 speed: Some(Speed::Fast),
1305 ..Default::default()
1306 };
1307
1308 let bumped = pc
1309 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1310 .await
1311 .unwrap();
1312 assert_eq!(
1313 bumped.is_min_bumped,
1314 Some(true),
1315 "Should be min bumped when prices are high enough"
1316 );
1317
1318 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1320 gas_price_cap: Some(50_000_000_000u128), ..Default::default()
1322 });
1323
1324 let tx_data = EvmTransactionData {
1325 max_fee_per_gas: Some(50_000_000_000),
1326 max_priority_fee_per_gas: Some(2_000_000_000),
1327 speed: Some(Speed::Fast),
1328 ..Default::default()
1329 };
1330
1331 let bumped = pc
1332 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1333 .await
1334 .unwrap();
1335 assert_eq!(
1337 bumped.is_min_bumped,
1338 Some(false),
1339 "Should not be min bumped when cap is too low"
1340 );
1341 }
1342
1343 #[tokio::test]
1344 async fn test_is_min_bumped_flag_legacy() {
1345 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1346 let mock_prices = GasPrices {
1347 legacy_prices: SpeedPrices {
1348 safe_low: 8_000_000_000,
1349 average: 10_000_000_000,
1350 fast: 12_000_000_000,
1351 fastest: 15_000_000_000,
1352 },
1353 max_priority_fee_per_gas: SpeedPrices::default(),
1354 base_fee_per_gas: 0,
1355 };
1356 mock_service
1357 .expect_get_prices_from_json_rpc()
1358 .returning(move || {
1359 let prices = mock_prices.clone();
1360 Box::pin(async move { Ok(prices) })
1361 });
1362 mock_service
1363 .expect_network()
1364 .return_const(create_mock_evm_network("mainnet"));
1365
1366 let pc = PriceCalculator::new(mock_service, None);
1367 let mut relayer = create_mock_relayer();
1368
1369 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1371 gas_price_cap: Some(100_000_000_000u128),
1372 ..Default::default()
1373 });
1374
1375 let tx_data = EvmTransactionData {
1376 gas_price: Some(10_000_000_000),
1377 speed: Some(Speed::Fast),
1378 ..Default::default()
1379 };
1380
1381 let bumped = pc
1382 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1383 .await
1384 .unwrap();
1385 assert_eq!(
1386 bumped.is_min_bumped,
1387 Some(true),
1388 "Should be min bumped with sufficient cap"
1389 );
1390
1391 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1393 gas_price_cap: Some(10_000_000_000u128), ..Default::default()
1395 });
1396
1397 let bumped = pc
1398 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1399 .await
1400 .unwrap();
1401 assert_eq!(
1402 bumped.is_min_bumped,
1403 Some(false),
1404 "Should not be min bumped with insufficient cap"
1405 );
1406 }
1407
1408 #[tokio::test]
1409 async fn test_calculate_bumped_gas_price_with_extra_fee() {
1410 let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
1412 mock_gas_service
1413 .expect_get_prices_from_json_rpc()
1414 .returning(|| {
1415 Box::pin(async {
1416 Ok(GasPrices {
1417 legacy_prices: SpeedPrices {
1418 safe_low: 10_000_000_000,
1419 average: 12_000_000_000,
1420 fast: 14_000_000_000,
1421 fastest: 18_000_000_000,
1422 },
1423 max_priority_fee_per_gas: SpeedPrices::default(),
1424 base_fee_per_gas: 40_000_000_000,
1425 })
1426 })
1427 });
1428 mock_gas_service
1429 .expect_network()
1430 .return_const(create_mock_evm_network("mainnet"));
1431
1432 let pc = PriceCalculator::new(mock_gas_service, None);
1434
1435 let relayer = create_mock_relayer();
1437 let tx_data = EvmTransactionData {
1438 max_fee_per_gas: Some(50_000_000_000),
1439 max_priority_fee_per_gas: Some(2_000_000_000),
1440 speed: Some(Speed::Fast),
1441 ..Default::default()
1442 };
1443
1444 let result = pc
1446 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1447 .await;
1448
1449 assert!(result.is_ok());
1451 let price_params = result.unwrap();
1452 assert_eq!(price_params.extra_fee, None);
1453 }
1454
1455 #[tokio::test]
1456 async fn test_total_cost_recomputed_without_overrider_legacy() {
1457 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1458 let mock_prices = GasPrices {
1459 legacy_prices: SpeedPrices {
1460 safe_low: 10_000_000_000,
1461 average: 12_000_000_000,
1462 fast: 14_000_000_000,
1463 fastest: 18_000_000_000,
1464 },
1465 max_priority_fee_per_gas: SpeedPrices::default(),
1466 base_fee_per_gas: 0,
1467 };
1468 mock_service
1469 .expect_get_prices_from_json_rpc()
1470 .returning(move || {
1471 let prices = mock_prices.clone();
1472 Box::pin(async move { Ok(prices) })
1473 });
1474 mock_service
1475 .expect_network()
1476 .return_const(create_mock_evm_network("mainnet"));
1477
1478 let pc = PriceCalculator::new(mock_service, None);
1479 let relayer = create_mock_relayer();
1480
1481 let gas_limit = 21_000u64;
1482 let tx_data = EvmTransactionData {
1483 gas_price: Some(20_000_000_000),
1484 gas_limit: Some(gas_limit),
1485 value: U256::ZERO,
1486 ..Default::default()
1487 };
1488
1489 let params = pc
1490 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1491 .await
1492 .unwrap();
1493
1494 let expected = U256::from(params.gas_price.unwrap()) * U256::from(gas_limit);
1496 assert_eq!(params.total_cost, expected);
1497 }
1498
1499 #[tokio::test]
1500 async fn test_total_cost_respected_with_overrider_nonzero_total_legacy() {
1501 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1502 let mock_prices = GasPrices {
1503 legacy_prices: SpeedPrices {
1504 safe_low: 10_000_000_000,
1505 average: 12_000_000_000,
1506 fast: 14_000_000_000,
1507 fastest: 18_000_000_000,
1508 },
1509 max_priority_fee_per_gas: SpeedPrices::default(),
1510 base_fee_per_gas: 0,
1511 };
1512 mock_service
1513 .expect_get_prices_from_json_rpc()
1514 .returning(move || {
1515 let prices = mock_prices.clone();
1516 Box::pin(async move { Ok(prices) })
1517 });
1518 mock_service
1519 .expect_network()
1520 .return_const(create_mock_evm_network("mainnet"));
1521
1522 let handler = Some(PriceParamsHandler::Mock(MockPriceHandler::new()));
1523 let pc = PriceCalculator::new(mock_service, handler);
1524 let relayer = create_mock_relayer();
1525
1526 let tx_data = EvmTransactionData {
1527 gas_price: Some(20_000_000_000),
1528 gas_limit: Some(21_000),
1529 value: U256::ZERO,
1530 ..Default::default()
1531 };
1532
1533 let params = pc
1534 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1535 .await
1536 .unwrap();
1537
1538 assert_eq!(params.extra_fee, Some(U256::from(42u128)));
1541 assert_eq!(params.total_cost, U256::from(42u128));
1542 }
1543
1544 #[tokio::test]
1545 async fn test_get_transaction_price_params_with_mock_overrider_legacy() {
1546 use crate::services::gas::handlers::test_mock::MockPriceHandler;
1547
1548 let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
1549 mock_gas_service
1550 .expect_get_legacy_prices_from_json_rpc()
1551 .returning(|| Box::pin(async { Ok(SpeedPrices::default()) }));
1552 mock_gas_service
1553 .expect_network()
1554 .return_const(create_mock_evm_network("mainnet"));
1555
1556 let price_params_handler = Some(PriceParamsHandler::Mock(MockPriceHandler::new()));
1557 let pc = PriceCalculator::new(mock_gas_service, price_params_handler);
1558
1559 let relayer = create_mock_relayer();
1560 let tx_data = EvmTransactionData {
1561 gas_price: Some(20_000_000_000),
1562 ..Default::default()
1563 };
1564
1565 let params = pc
1566 .get_transaction_price_params(&tx_data, &relayer)
1567 .await
1568 .unwrap();
1569 assert_eq!(params.extra_fee, Some(U256::from(42u128)));
1570 assert!(params.total_cost >= U256::from(42u128));
1571 }
1572
1573 #[tokio::test]
1574 async fn test_get_transaction_price_params_with_mock_overrider_eip1559() {
1575 use crate::services::gas::handlers::test_mock::MockPriceHandler;
1576
1577 let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
1578 let mock_prices = GasPrices {
1579 legacy_prices: SpeedPrices::default(),
1580 max_priority_fee_per_gas: SpeedPrices::default(),
1581 base_fee_per_gas: 50_000_000_000,
1582 };
1583 mock_gas_service
1584 .expect_get_prices_from_json_rpc()
1585 .returning(move || {
1586 let prices = mock_prices.clone();
1587 Box::pin(async move { Ok(prices) })
1588 });
1589 mock_gas_service
1590 .expect_network()
1591 .return_const(create_mock_evm_network("mainnet"));
1592
1593 let price_params_handler = Some(PriceParamsHandler::Mock(MockPriceHandler::new()));
1594 let pc = PriceCalculator::new(mock_gas_service, price_params_handler);
1595
1596 let relayer = create_mock_relayer();
1597 let tx_data = EvmTransactionData {
1598 max_fee_per_gas: Some(30_000_000_000),
1599 max_priority_fee_per_gas: Some(2_000_000_000),
1600 ..Default::default()
1601 };
1602
1603 let params = pc
1604 .get_transaction_price_params(&tx_data, &relayer)
1605 .await
1606 .unwrap();
1607 assert_eq!(params.extra_fee, Some(U256::from(42u128)));
1608 assert!(params.total_cost >= U256::from(42u128));
1609 }
1610
1611 #[tokio::test]
1612 async fn test_get_transaction_price_params_recompute_without_overrider_legacy() {
1613 let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
1614 mock_gas_service
1615 .expect_get_legacy_prices_from_json_rpc()
1616 .returning(|| Box::pin(async { Ok(SpeedPrices::default()) }));
1617 mock_gas_service
1618 .expect_network()
1619 .return_const(create_mock_evm_network("mainnet"));
1620
1621 let pc = PriceCalculator::new(mock_gas_service, None);
1622 let relayer = create_mock_relayer();
1623
1624 let gas_limit = 21_000u64;
1625 let tx_data = EvmTransactionData {
1626 gas_price: Some(20_000_000_000),
1627 gas_limit: Some(gas_limit),
1628 value: U256::ZERO,
1629 ..Default::default()
1630 };
1631
1632 let params = pc
1633 .get_transaction_price_params(&tx_data, &relayer)
1634 .await
1635 .unwrap();
1636
1637 let expected = U256::from(params.gas_price.unwrap()) * U256::from(gas_limit);
1638 assert_eq!(params.total_cost, expected);
1639 }
1640
1641 #[test]
1642 fn test_calculate_total_cost_eip1559() {
1643 let price_params = PriceParams {
1644 gas_price: None,
1645 max_fee_per_gas: Some(30_000_000_000),
1646 max_priority_fee_per_gas: Some(2_000_000_000),
1647 is_min_bumped: None,
1648 extra_fee: None,
1649 total_cost: U256::ZERO,
1650 };
1651
1652 let gas_limit = 100_000;
1653 let value = U256::from(1_000_000_000_000_000_000u128); let is_eip1559 = true;
1655
1656 let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1657
1658 let expected = U256::from(30_000_000_000u128) * U256::from(gas_limit) + value;
1660 assert_eq!(total_cost, expected);
1661 }
1662
1663 #[test]
1664 fn test_calculate_total_cost_legacy() {
1665 let price_params = PriceParams {
1666 gas_price: Some(20_000_000_000),
1667 max_fee_per_gas: None,
1668 max_priority_fee_per_gas: None,
1669 is_min_bumped: None,
1670 extra_fee: None,
1671 total_cost: U256::ZERO,
1672 };
1673
1674 let gas_limit = 100_000;
1675 let value = U256::from(1_000_000_000_000_000_000u128); let is_eip1559 = false;
1677
1678 let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1679
1680 let expected = U256::from(20_000_000_000u128) * U256::from(gas_limit) + value;
1682 assert_eq!(total_cost, expected);
1683 }
1684
1685 #[test]
1686 fn test_calculate_total_cost_with_extra_fee() {
1687 let price_params = PriceParams {
1688 gas_price: Some(20_000_000_000),
1689 max_fee_per_gas: None,
1690 max_priority_fee_per_gas: None,
1691 is_min_bumped: None,
1692 extra_fee: Some(U256::from(5_000_000_000u128)),
1693 total_cost: U256::ZERO,
1694 };
1695
1696 let gas_limit = 100_000;
1697 let value = U256::from(1_000_000_000_000_000_000u128); let is_eip1559 = false;
1699
1700 let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1701
1702 let expected = U256::from(20_000_000_000u128) * U256::from(gas_limit)
1703 + value
1704 + U256::from(5_000_000_000u128);
1705 assert_eq!(total_cost, expected);
1706 }
1707
1708 #[test]
1709 fn test_calculate_total_cost_zero_values() {
1710 let price_params = PriceParams {
1711 gas_price: Some(0),
1712 max_fee_per_gas: Some(0),
1713 max_priority_fee_per_gas: Some(0),
1714 is_min_bumped: None,
1715 extra_fee: Some(U256::ZERO),
1716 total_cost: U256::ZERO,
1717 };
1718
1719 let gas_limit = 0;
1720 let value = U256::from(0);
1721
1722 let legacy_total_cost = price_params.calculate_total_cost(false, gas_limit, value);
1723 assert_eq!(legacy_total_cost, U256::ZERO);
1724
1725 let eip1559_total_cost = price_params.calculate_total_cost(true, gas_limit, value);
1726 assert_eq!(eip1559_total_cost, U256::ZERO);
1727 }
1728
1729 #[test]
1730 fn test_calculate_total_cost_missing_values() {
1731 let price_params = PriceParams {
1732 gas_price: None,
1733 max_fee_per_gas: None,
1734 max_priority_fee_per_gas: None,
1735 is_min_bumped: None,
1736 extra_fee: None,
1737 total_cost: U256::ZERO,
1738 };
1739
1740 let gas_limit = 100_000;
1741 let value = U256::from(1_000_000_000_000_000_000u128);
1742
1743 let legacy_total = price_params.calculate_total_cost(false, gas_limit, value);
1744 assert_eq!(legacy_total, value);
1745
1746 let eip1559_total = price_params.calculate_total_cost(true, gas_limit, value);
1747 assert_eq!(eip1559_total, value);
1748 }
1749
1750 #[test]
1751 fn test_calculate_min_bump_normal_cases() {
1752 let base_price = 20_000_000_000u128; let expected = 22_000_000_000u128; assert_eq!(calculate_min_bump(base_price), expected);
1755
1756 let base_price = 1_000_000_000u128;
1757 let expected = 1_100_000_000u128; assert_eq!(calculate_min_bump(base_price), expected);
1759
1760 let base_price = 100_000_000_000u128;
1761 let expected = 110_000_000_000u128; assert_eq!(calculate_min_bump(base_price), expected);
1763 }
1764
1765 #[test]
1766 fn test_calculate_min_bump_edge_cases() {
1767 assert_eq!(calculate_min_bump(0), 1);
1769
1770 let result = calculate_min_bump(1);
1772 assert!(result >= 2);
1773
1774 let base_price = 5u128; let result = calculate_min_bump(base_price);
1777 assert!(
1778 result > base_price,
1779 "Result {result} should be greater than base_price {base_price}"
1780 );
1781
1782 let base_price = 9u128;
1783 let result = calculate_min_bump(base_price);
1784 assert_eq!(
1785 result, 10u128,
1786 "9 wei should bump to 10 wei (minimum 1 wei increase)"
1787 );
1788 }
1789
1790 #[test]
1791 fn test_calculate_min_bump_large_values() {
1792 let base_price = u128::MAX / 2;
1794 let result = calculate_min_bump(base_price);
1795 assert!(result > base_price);
1796
1797 let base_price = u128::MAX - 1000;
1799 let result = calculate_min_bump(base_price);
1800 assert!(result >= base_price.saturating_add(1));
1802 }
1803
1804 #[test]
1805 fn test_calculate_min_bump_overflow_protection() {
1806 let base_price = u128::MAX;
1807 let result = calculate_min_bump(base_price);
1808 assert_eq!(result, u128::MAX);
1809
1810 let base_price = (u128::MAX / 11) * 10 + 1;
1811 let result = calculate_min_bump(base_price);
1812 assert!(result >= base_price);
1813 }
1814
1815 #[test]
1816 fn test_calculate_min_bump_minimum_increase_guarantee() {
1817 let test_cases = vec![0, 1, 2, 5, 9, 10, 100, 1000, 10000];
1819
1820 for base_price in test_cases {
1821 let result = calculate_min_bump(base_price);
1822 assert!(
1823 result > base_price,
1824 "calculate_min_bump({base_price}) = {result} should be greater than base_price"
1825 );
1826 assert!(
1827 result >= base_price.saturating_add(1),
1828 "calculate_min_bump({base_price}) = {result} should be at least base_price + 1"
1829 );
1830 }
1831 }
1832
1833 #[tokio::test]
1834 async fn test_calculate_bumped_gas_price_no_mempool_network_eip1559() {
1835 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1836
1837 mock_service
1839 .expect_network()
1840 .return_const(create_mock_no_mempool_network("arbitrum"));
1841
1842 let mock_prices = GasPrices {
1844 legacy_prices: SpeedPrices {
1845 safe_low: 1_000_000_000,
1846 average: 2_000_000_000,
1847 fast: 3_000_000_000,
1848 fastest: 4_000_000_000,
1849 },
1850 max_priority_fee_per_gas: SpeedPrices {
1851 safe_low: 1_000_000_000,
1852 average: 2_000_000_000,
1853 fast: 3_000_000_000,
1854 fastest: 4_000_000_000,
1855 },
1856 base_fee_per_gas: 50_000_000_000,
1857 };
1858
1859 mock_service
1860 .expect_get_prices_from_json_rpc()
1861 .returning(move || {
1862 let prices = mock_prices.clone();
1863 Box::pin(async move { Ok(prices) })
1864 });
1865
1866 mock_service
1868 .expect_get_legacy_prices_from_json_rpc()
1869 .returning(|| {
1870 Box::pin(async {
1871 Ok(SpeedPrices {
1872 safe_low: 1_000_000_000,
1873 average: 2_000_000_000,
1874 fast: 3_000_000_000,
1875 fastest: 4_000_000_000,
1876 })
1877 })
1878 });
1879
1880 let pc = PriceCalculator::new(mock_service, None);
1881 let mut relayer = create_mock_relayer();
1882
1883 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1885 eip1559_pricing: Some(true),
1886 ..Default::default()
1887 });
1888
1889 let tx_data = EvmTransactionData {
1891 speed: Some(Speed::Fast),
1892 gas_limit: Some(21000),
1893 value: U256::from(1_000_000_000_000_000_000u128), ..Default::default()
1895 };
1896
1897 let result = pc
1898 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1899 .await;
1900
1901 assert!(result.is_ok());
1902 let price_params = result.unwrap();
1903
1904 assert_eq!(price_params.is_min_bumped, Some(true));
1906
1907 assert!(
1913 price_params.max_priority_fee_per_gas.is_some() || price_params.gas_price.is_some()
1914 );
1915
1916 assert!(price_params.total_cost > U256::ZERO);
1918 }
1919
1920 #[tokio::test]
1921 async fn test_calculate_bumped_gas_price_no_mempool_network_legacy() {
1922 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1923
1924 mock_service
1926 .expect_network()
1927 .return_const(create_mock_no_mempool_network("arbitrum"));
1928
1929 let mock_legacy_prices = SpeedPrices {
1931 safe_low: 10_000_000_000,
1932 average: 12_000_000_000,
1933 fast: 14_000_000_000,
1934 fastest: 18_000_000_000,
1935 };
1936
1937 mock_service
1938 .expect_get_legacy_prices_from_json_rpc()
1939 .returning(move || {
1940 let prices = mock_legacy_prices.clone();
1941 Box::pin(async move { Ok(prices) })
1942 });
1943
1944 let pc = PriceCalculator::new(mock_service, None);
1945 let mut relayer = create_mock_relayer();
1946
1947 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1949 eip1559_pricing: Some(false),
1950 ..Default::default()
1951 });
1952
1953 let tx_data = EvmTransactionData {
1955 speed: Some(Speed::Fast),
1956 gas_limit: Some(21000),
1957 value: U256::from(1_000_000_000_000_000_000u128), ..Default::default()
1959 };
1960
1961 let result = pc
1962 .calculate_bumped_gas_price(&tx_data, &relayer, false)
1963 .await;
1964
1965 assert!(result.is_ok());
1966 let price_params = result.unwrap();
1967
1968 assert_eq!(price_params.is_min_bumped, Some(true));
1970
1971 assert!(
1973 price_params.max_priority_fee_per_gas.is_some() || price_params.gas_price.is_some()
1974 );
1975
1976 assert!(price_params.total_cost > U256::ZERO);
1978 }
1979
1980 #[tokio::test]
1981 async fn test_calculate_bumped_gas_price_no_mempool_vs_regular_network() {
1982 let mut mock_service_regular = MockEvmGasPriceServiceTrait::new();
1984
1985 let mock_prices = GasPrices {
1986 legacy_prices: SpeedPrices::default(),
1987 max_priority_fee_per_gas: SpeedPrices {
1988 safe_low: 1_000_000_000,
1989 average: 2_000_000_000,
1990 fast: 3_000_000_000,
1991 fastest: 4_000_000_000,
1992 },
1993 base_fee_per_gas: 50_000_000_000,
1994 };
1995
1996 mock_service_regular
1997 .expect_network()
1998 .return_const(create_mock_evm_network("mainnet"));
1999
2000 let mock_prices_clone = mock_prices.clone();
2001 mock_service_regular
2002 .expect_get_prices_from_json_rpc()
2003 .returning(move || {
2004 let prices = mock_prices_clone.clone();
2005 Box::pin(async move { Ok(prices) })
2006 });
2007
2008 let pc_regular = PriceCalculator::new(mock_service_regular, None);
2009 let relayer = create_mock_relayer();
2010
2011 let tx_data = EvmTransactionData {
2012 speed: Some(Speed::Fast),
2013 ..Default::default()
2014 };
2015
2016 let result_regular = pc_regular
2017 .calculate_bumped_gas_price(&tx_data, &relayer, false)
2018 .await
2019 .unwrap();
2020
2021 assert!(
2023 result_regular.max_priority_fee_per_gas.is_some() || result_regular.gas_price.is_some()
2024 );
2025
2026 let mut mock_service_no_mempool = MockEvmGasPriceServiceTrait::new();
2028
2029 mock_service_no_mempool
2030 .expect_network()
2031 .return_const(create_mock_no_mempool_network("arbitrum"));
2032
2033 mock_service_no_mempool
2034 .expect_get_prices_from_json_rpc()
2035 .returning(move || {
2036 let prices = mock_prices.clone();
2037 Box::pin(async move { Ok(prices) })
2038 });
2039
2040 let pc_no_mempool = PriceCalculator::new(mock_service_no_mempool, None);
2041
2042 let result_no_mempool = pc_no_mempool
2043 .calculate_bumped_gas_price(&tx_data, &relayer, false)
2044 .await
2045 .unwrap();
2046
2047 assert_eq!(result_no_mempool.is_min_bumped, Some(true));
2049
2050 assert!(
2052 result_no_mempool.max_priority_fee_per_gas.is_some()
2053 || result_no_mempool.gas_price.is_some()
2054 );
2055
2056 assert_eq!(result_no_mempool.is_min_bumped, Some(true));
2059 }
2060
2061 #[tokio::test]
2062 async fn test_calculate_bumped_gas_price_arbitrum_network() {
2063 let mut mock_service = MockEvmGasPriceServiceTrait::new();
2064
2065 use crate::models::RpcConfig;
2067 let arbitrum_network = EvmNetwork {
2068 network: "arbitrum-one".to_string(),
2069 rpc_urls: vec![RpcConfig::new("https://arb1.arbitrum.io/rpc".to_string())],
2070 explorer_urls: None,
2071 average_blocktime_ms: 1000, is_testnet: false,
2073 tags: vec![ARBITRUM_BASED_TAG.to_string()], chain_id: 42161,
2075 required_confirmations: 1,
2076 features: vec!["eip1559".to_string()],
2077 symbol: "ETH".to_string(),
2078 gas_price_cache: None,
2079 };
2080
2081 mock_service.expect_network().return_const(arbitrum_network);
2083
2084 let mock_prices = GasPrices {
2086 legacy_prices: SpeedPrices {
2087 safe_low: 100_000_000, average: 200_000_000,
2089 fast: 300_000_000,
2090 fastest: 400_000_000,
2091 },
2092 max_priority_fee_per_gas: SpeedPrices {
2093 safe_low: 10_000_000, average: 20_000_000,
2095 fast: 30_000_000,
2096 fastest: 40_000_000,
2097 },
2098 base_fee_per_gas: 100_000_000, };
2100
2101 mock_service
2102 .expect_get_prices_from_json_rpc()
2103 .returning(move || {
2104 let prices = mock_prices.clone();
2105 Box::pin(async move { Ok(prices) })
2106 });
2107
2108 let pc = PriceCalculator::new(mock_service, None);
2109 let relayer = create_mock_relayer();
2110
2111 let tx_data = EvmTransactionData {
2113 speed: Some(Speed::Fast),
2114 gas_limit: Some(21000),
2115 value: U256::from(1_000_000_000_000_000_000u128), ..Default::default()
2117 };
2118
2119 let result = pc
2120 .calculate_bumped_gas_price(&tx_data, &relayer, false)
2121 .await;
2122
2123 assert!(result.is_ok());
2124 let price_params = result.unwrap();
2125
2126 assert_eq!(
2128 price_params.is_min_bumped,
2129 Some(true),
2130 "Arbitrum networks should skip bumping and use current market prices"
2131 );
2132
2133 assert!(
2135 price_params.max_priority_fee_per_gas.is_some() || price_params.gas_price.is_some(),
2136 "Should return some form of pricing"
2137 );
2138
2139 assert!(
2141 price_params.total_cost > U256::ZERO,
2142 "Should have non-zero total cost"
2143 );
2144
2145 if let Some(priority_fee) = price_params.max_priority_fee_per_gas {
2148 assert!(
2150 priority_fee <= 50_000_000, "Priority fee should be based on current market, not bumped: {priority_fee}"
2152 );
2153 }
2154 }
2155
2156 #[tokio::test]
2157 async fn test_calculate_bumped_gas_price_force_bump_bypasses_cap() {
2158 let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
2160
2161 let mock_prices = GasPrices {
2162 legacy_prices: SpeedPrices {
2163 safe_low: 100_000_000_000,
2164 average: 120_000_000_000,
2165 fast: 150_000_000_000, fastest: 200_000_000_000,
2167 },
2168 max_priority_fee_per_gas: SpeedPrices {
2169 safe_low: 1_000_000_000,
2170 average: 2_000_000_000,
2171 fast: 3_000_000_000,
2172 fastest: 5_000_000_000,
2173 },
2174 base_fee_per_gas: 50_000_000_000,
2175 };
2176
2177 mock_gas_service
2178 .expect_get_prices_from_json_rpc()
2179 .returning(move || {
2180 let prices = mock_prices.clone();
2181 Box::pin(async move { Ok(prices) })
2182 });
2183 mock_gas_service
2184 .expect_network()
2185 .return_const(create_mock_evm_network("mainnet"));
2186
2187 let pc = PriceCalculator::new(mock_gas_service, None);
2188
2189 let tx_data = EvmTransactionData {
2190 gas_price: Some(100_000_000_000), gas_limit: Some(21000),
2192 nonce: Some(1),
2193 speed: Some(Speed::Fast),
2194 ..Default::default()
2195 };
2196
2197 let mut relayer = create_mock_relayer();
2198 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
2199 gas_price_cap: Some(120_000_000_000), ..Default::default()
2201 });
2202
2203 let result_capped = pc
2205 .calculate_bumped_gas_price(&tx_data, &relayer, false)
2206 .await
2207 .unwrap();
2208
2209 assert_eq!(
2211 result_capped.gas_price.unwrap(),
2212 120_000_000_000,
2213 "With force_bump=false, gas price should be capped at 120 Gwei"
2214 );
2215 assert!(
2216 result_capped.is_min_bumped.unwrap(),
2217 "Should still meet minimum bump requirement"
2218 );
2219
2220 let result_uncapped = pc
2222 .calculate_bumped_gas_price(&tx_data, &relayer, true)
2223 .await
2224 .unwrap();
2225
2226 assert_eq!(
2228 result_uncapped.gas_price.unwrap(),
2229 150_000_000_000,
2230 "With force_bump=true, gas price should NOT be capped and use market price of 150 Gwei"
2231 );
2232 assert!(
2233 result_uncapped.is_min_bumped.unwrap(),
2234 "Should meet minimum bump requirement"
2235 );
2236
2237 assert!(
2239 result_uncapped.gas_price.unwrap() > result_capped.gas_price.unwrap(),
2240 "force_bump=true should result in higher gas price than capped version"
2241 );
2242 }
2243}