1use crate::{
38 constants::{DEFAULT_GAS_LIMIT, DEFAULT_TRANSACTION_SPEED},
39 models::{
40 evm::Speed, EvmNetwork, EvmTransactionData, EvmTransactionDataTrait, RelayerRepoModel,
41 TransactionError, U256,
42 },
43 services::{
44 gas::{EvmGasPriceServiceTrait, NetworkExtraFeeCalculatorServiceTrait},
45 GasPrices, NetworkExtraFeeCalculator,
46 },
47};
48
49#[cfg(test)]
50use mockall::automock;
51
52#[cfg(test)]
53use crate::services::gas::MockNetworkExtraFeeCalculatorServiceTrait;
54
55#[async_trait::async_trait]
56#[cfg_attr(test, automock)]
57pub trait PriceCalculatorTrait: Send + Sync {
58 async fn get_transaction_price_params(
59 &self,
60 tx_data: &EvmTransactionData,
61 relayer: &RelayerRepoModel,
62 ) -> Result<PriceParams, TransactionError>;
63
64 async fn calculate_bumped_gas_price(
65 &self,
66 tx_data: &EvmTransactionData,
67 relayer: &RelayerRepoModel,
68 ) -> Result<PriceParams, TransactionError>;
69}
70
71type GasPriceCapResult = (Option<u128>, Option<u128>, Option<u128>);
72
73const PRECISION: u128 = 1_000_000_000; const MINUTE_AND_HALF_MS: u128 = 90000;
75const BASE_FEE_INCREASE_FACTOR_PERCENT: u128 = 125; const MAX_BASE_FEE_MULTIPLIER: u128 = 10 * PRECISION; #[derive(Debug, Clone)]
79pub struct PriceParams {
80 pub gas_price: Option<u128>,
81 pub max_fee_per_gas: Option<u128>,
82 pub max_priority_fee_per_gas: Option<u128>,
83 pub is_min_bumped: Option<bool>,
84 pub extra_fee: Option<u128>,
85 pub total_cost: U256,
86}
87
88impl PriceParams {
89 pub fn calculate_total_cost(&self, is_eip1559: bool, gas_limit: u64, value: U256) -> U256 {
90 match is_eip1559 {
91 true => {
92 U256::from(self.max_fee_per_gas.unwrap_or(0)) * U256::from(gas_limit)
93 + value
94 + U256::from(self.extra_fee.unwrap_or(0))
95 }
96 false => {
97 U256::from(self.gas_price.unwrap_or(0)) * U256::from(gas_limit)
98 + value
99 + U256::from(self.extra_fee.unwrap_or(0))
100 }
101 }
102 }
103}
104
105pub fn calculate_min_bump(base_price: u128) -> u128 {
116 const BUMP_NUMERATOR: u128 = 11;
119 const BUMP_DENOMINATOR: u128 = 10;
120
121 let bumped_price = base_price
122 .saturating_mul(BUMP_NUMERATOR)
123 .saturating_div(BUMP_DENOMINATOR);
124
125 std::cmp::max(bumped_price, base_price.saturating_add(1))
127}
128
129pub struct PriceCalculator<G: EvmGasPriceServiceTrait> {
131 gas_price_service: G,
132 network_extra_fee_calculator_service: NetworkExtraFeeCalculator<G::Provider>,
133}
134
135#[async_trait::async_trait]
136impl<G: EvmGasPriceServiceTrait + Send + Sync> PriceCalculatorTrait for PriceCalculator<G> {
137 async fn get_transaction_price_params(
138 &self,
139 tx_data: &EvmTransactionData,
140 relayer: &RelayerRepoModel,
141 ) -> Result<PriceParams, TransactionError> {
142 self.get_transaction_price_params(tx_data, relayer).await
143 }
144
145 async fn calculate_bumped_gas_price(
146 &self,
147 tx_data: &EvmTransactionData,
148 relayer: &RelayerRepoModel,
149 ) -> Result<PriceParams, TransactionError> {
150 self.calculate_bumped_gas_price(tx_data, relayer).await
151 }
152}
153
154impl<G: EvmGasPriceServiceTrait> PriceCalculator<G> {
155 pub fn new(
156 gas_price_service: G,
157 network_extra_fee_calculator_service: NetworkExtraFeeCalculator<G::Provider>,
158 ) -> Self {
159 Self {
160 gas_price_service,
161 network_extra_fee_calculator_service,
162 }
163 }
164
165 pub async fn get_transaction_price_params(
181 &self,
182 tx_data: &EvmTransactionData,
183 relayer: &RelayerRepoModel,
184 ) -> Result<PriceParams, TransactionError> {
185 let price_params = self
186 .fetch_price_params_based_on_tx_type(tx_data, relayer)
187 .await?;
188 let (gas_price_capped, max_fee_per_gas_capped, max_priority_fee_per_gas_capped) = self
189 .apply_gas_price_cap(
190 price_params.gas_price.unwrap_or_default(),
191 price_params.max_fee_per_gas,
192 price_params.max_priority_fee_per_gas,
193 relayer,
194 )?;
195
196 let mut final_params = PriceParams {
197 gas_price: gas_price_capped,
198 max_fee_per_gas: max_fee_per_gas_capped,
199 max_priority_fee_per_gas: max_priority_fee_per_gas_capped,
200 is_min_bumped: None,
201 extra_fee: None,
202 total_cost: U256::ZERO,
203 };
204
205 match &self.network_extra_fee_calculator_service {
206 NetworkExtraFeeCalculator::None => {}
207 _ => {
208 let new_tx = tx_data.clone().with_price_params(final_params.clone());
209 let extra_fee = self
210 .network_extra_fee_calculator_service
211 .get_extra_fee(&new_tx)
212 .await?;
213 final_params.extra_fee = Some(extra_fee.try_into().unwrap_or(0));
214 }
215 }
216
217 final_params.total_cost = final_params.calculate_total_cost(
218 tx_data.is_eip1559(),
219 tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT), U256::from(tx_data.value),
221 );
222
223 Ok(final_params)
224 }
225
226 pub async fn calculate_bumped_gas_price(
240 &self,
241 tx_data: &EvmTransactionData,
242 relayer: &RelayerRepoModel,
243 ) -> Result<PriceParams, TransactionError> {
244 if self.gas_price_service.network().lacks_mempool()
246 || self.gas_price_service.network().is_arbitrum()
247 {
248 let mut price_params = self.get_transaction_price_params(tx_data, relayer).await?;
249
250 price_params.is_min_bumped = Some(true);
252 return Ok(price_params);
253 }
254
255 let network_gas_prices = self.gas_price_service.get_prices_from_json_rpc().await?;
256 let relayer_gas_price_cap = relayer
257 .policies
258 .get_evm_policy()
259 .gas_price_cap
260 .unwrap_or(u128::MAX);
261
262 let bumped_price_params = match (
264 tx_data.max_fee_per_gas,
265 tx_data.max_priority_fee_per_gas,
266 tx_data.gas_price,
267 ) {
268 (Some(max_fee), Some(max_priority_fee), _) => {
269 self.handle_eip1559_bump(
271 &network_gas_prices,
272 relayer_gas_price_cap,
273 tx_data.speed.as_ref(),
274 max_fee,
275 max_priority_fee,
276 )?
277 }
278 (None, None, Some(gas_price)) => {
279 self.handle_legacy_bump(
281 &network_gas_prices,
282 relayer_gas_price_cap,
283 tx_data.speed.as_ref(),
284 gas_price,
285 )?
286 }
287 _ => {
288 return Err(TransactionError::InvalidType(
289 "Transaction missing required gas price parameters".to_string(),
290 ))
291 }
292 };
293
294 let mut final_params = bumped_price_params;
296 let value = tx_data.value;
297 let gas_limit = tx_data.gas_limit;
298 let is_eip1559 = tx_data.is_eip1559();
299
300 match &self.network_extra_fee_calculator_service {
301 NetworkExtraFeeCalculator::None => {}
302 _ => {
303 let new_tx = tx_data.clone().with_price_params(final_params.clone());
304 let extra_fee = self
305 .network_extra_fee_calculator_service
306 .get_extra_fee(&new_tx)
307 .await?;
308 final_params.extra_fee = Some(extra_fee.try_into().unwrap_or(0));
309 }
310 }
311
312 final_params.total_cost = final_params.calculate_total_cost(
313 is_eip1559,
314 gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
315 U256::from(value),
316 );
317
318 Ok(final_params)
319 }
320
321 fn handle_eip1559_bump(
337 &self,
338 network_gas_prices: &GasPrices,
339 gas_price_cap: u128,
340 maybe_speed: Option<&Speed>,
341 max_fee: u128,
342 max_priority_fee: u128,
343 ) -> Result<PriceParams, TransactionError> {
344 let speed = maybe_speed.unwrap_or(&DEFAULT_TRANSACTION_SPEED);
345
346 let min_bump_max_fee = calculate_min_bump(max_fee);
348 let min_bump_max_priority = calculate_min_bump(max_priority_fee);
349
350 let current_market_priority =
352 Self::get_market_price_for_speed(network_gas_prices, true, speed);
353
354 let bumped_priority_fee = if current_market_priority >= min_bump_max_priority {
358 current_market_priority
359 } else {
360 min_bump_max_priority
361 };
362
363 let base_fee_wei = network_gas_prices.base_fee_per_gas;
366 let bumped_max_fee_per_gas = if base_fee_wei >= min_bump_max_fee {
367 base_fee_wei
368 } else {
369 min_bump_max_fee
370 };
371
372 let recommended_max_fee_per_gas = calculate_max_fee_per_gas(
375 base_fee_wei,
376 bumped_priority_fee,
377 self.gas_price_service.network(),
378 );
379
380 let final_max_fee = std::cmp::max(bumped_max_fee_per_gas, recommended_max_fee_per_gas);
382
383 let capped_priority = Self::cap_gas_price(bumped_priority_fee, gas_price_cap);
385 let capped_max_fee = Self::cap_gas_price(final_max_fee, gas_price_cap);
386
387 let is_min_bumped =
389 capped_priority >= min_bump_max_priority && capped_max_fee >= min_bump_max_fee;
390
391 Ok(PriceParams {
393 gas_price: None,
394 max_priority_fee_per_gas: Some(capped_priority),
395 max_fee_per_gas: Some(capped_max_fee),
396 is_min_bumped: Some(is_min_bumped),
397 extra_fee: None,
398 total_cost: U256::ZERO,
399 })
400 }
401
402 fn handle_legacy_bump(
407 &self,
408 network_gas_prices: &GasPrices,
409 gas_price_cap: u128,
410 maybe_speed: Option<&Speed>,
411 gas_price: u128,
412 ) -> Result<PriceParams, TransactionError> {
413 let speed = maybe_speed.unwrap_or(&Speed::Fast);
414
415 let min_bump_gas_price = calculate_min_bump(gas_price);
417
418 let current_market_price =
420 Self::get_market_price_for_speed(network_gas_prices, false, speed);
421
422 let bumped_gas_price = if current_market_price >= min_bump_gas_price {
423 current_market_price
424 } else {
425 min_bump_gas_price
426 };
427
428 let capped_gas_price = Self::cap_gas_price(bumped_gas_price, gas_price_cap);
430
431 let is_min_bumped = capped_gas_price >= min_bump_gas_price;
433
434 Ok(PriceParams {
435 gas_price: Some(capped_gas_price),
436 max_priority_fee_per_gas: None,
437 max_fee_per_gas: None,
438 is_min_bumped: Some(is_min_bumped),
439 extra_fee: None,
440 total_cost: U256::ZERO,
441 })
442 }
443 async fn fetch_price_params_based_on_tx_type(
445 &self,
446 tx_data: &EvmTransactionData,
447 relayer: &RelayerRepoModel,
448 ) -> Result<PriceParams, TransactionError> {
449 if tx_data.is_legacy() {
450 self.fetch_legacy_price_params(tx_data)
451 } else if tx_data.is_eip1559() {
452 self.fetch_eip1559_price_params(tx_data)
453 } else if tx_data.is_speed() {
454 self.fetch_speed_price_params(tx_data, relayer).await
455 } else {
456 Err(TransactionError::NotSupported(
457 "Invalid transaction type".to_string(),
458 ))
459 }
460 }
461
462 fn fetch_legacy_price_params(
470 &self,
471 tx_data: &EvmTransactionData,
472 ) -> Result<PriceParams, TransactionError> {
473 let gas_price = tx_data.gas_price.ok_or(TransactionError::NotSupported(
474 "Gas price is required for legacy transactions".to_string(),
475 ))?;
476 Ok(PriceParams {
477 gas_price: Some(gas_price),
478 max_fee_per_gas: None,
479 max_priority_fee_per_gas: None,
480 is_min_bumped: None,
481 extra_fee: None,
482 total_cost: U256::ZERO,
483 })
484 }
485
486 fn fetch_eip1559_price_params(
487 &self,
488 tx_data: &EvmTransactionData,
489 ) -> Result<PriceParams, TransactionError> {
490 let max_fee = tx_data
491 .max_fee_per_gas
492 .ok_or(TransactionError::NotSupported(
493 "Max fee per gas is required for EIP1559 transactions".to_string(),
494 ))?;
495 let max_priority_fee =
496 tx_data
497 .max_priority_fee_per_gas
498 .ok_or(TransactionError::NotSupported(
499 "Max priority fee per gas is required for EIP1559 transactions".to_string(),
500 ))?;
501 Ok(PriceParams {
502 gas_price: None,
503 max_fee_per_gas: Some(max_fee),
504 max_priority_fee_per_gas: Some(max_priority_fee),
505 is_min_bumped: None,
506 extra_fee: None,
507 total_cost: U256::ZERO,
508 })
509 }
510 async fn fetch_speed_price_params(
515 &self,
516 tx_data: &EvmTransactionData,
517 relayer: &RelayerRepoModel,
518 ) -> Result<PriceParams, TransactionError> {
519 let speed = tx_data
520 .speed
521 .as_ref()
522 .ok_or(TransactionError::NotSupported(
523 "Speed is required".to_string(),
524 ))?;
525 let use_legacy = relayer.policies.get_evm_policy().eip1559_pricing == Some(false)
526 || self.gas_price_service.network().is_legacy();
527
528 if use_legacy {
529 self.fetch_legacy_speed_params(speed).await
530 } else {
531 self.fetch_eip1559_speed_params(speed).await
532 }
533 }
534
535 async fn fetch_eip1559_speed_params(
540 &self,
541 speed: &Speed,
542 ) -> Result<PriceParams, TransactionError> {
543 let prices = self.gas_price_service.get_prices_from_json_rpc().await?;
544 let priority_fee = match speed {
545 Speed::SafeLow => prices.max_priority_fee_per_gas.safe_low,
546 Speed::Average => prices.max_priority_fee_per_gas.average,
547 Speed::Fast => prices.max_priority_fee_per_gas.fast,
548 Speed::Fastest => prices.max_priority_fee_per_gas.fastest,
549 };
550 let max_fee = calculate_max_fee_per_gas(
551 prices.base_fee_per_gas,
552 priority_fee,
553 self.gas_price_service.network(),
554 );
555 Ok(PriceParams {
556 gas_price: None,
557 max_fee_per_gas: Some(max_fee),
558 max_priority_fee_per_gas: Some(priority_fee),
559 is_min_bumped: None,
560 extra_fee: None,
561 total_cost: U256::ZERO,
562 })
563 }
564 async fn fetch_legacy_speed_params(
569 &self,
570 speed: &Speed,
571 ) -> Result<PriceParams, TransactionError> {
572 let prices = self
573 .gas_price_service
574 .get_legacy_prices_from_json_rpc()
575 .await?;
576 let gas_price = match speed {
577 Speed::SafeLow => prices.safe_low,
578 Speed::Average => prices.average,
579 Speed::Fast => prices.fast,
580 Speed::Fastest => prices.fastest,
581 };
582 Ok(PriceParams {
583 gas_price: Some(gas_price),
584 max_fee_per_gas: None,
585 max_priority_fee_per_gas: None,
586 is_min_bumped: None,
587 extra_fee: None,
588 total_cost: U256::ZERO,
589 })
590 }
591
592 fn apply_gas_price_cap(
597 &self,
598 gas_price: u128,
599 max_fee_per_gas: Option<u128>,
600 max_priority_fee_per_gas: Option<u128>,
601 relayer: &RelayerRepoModel,
602 ) -> Result<GasPriceCapResult, TransactionError> {
603 let gas_price_cap = relayer
604 .policies
605 .get_evm_policy()
606 .gas_price_cap
607 .unwrap_or(u128::MAX);
608
609 if let (Some(max_fee), Some(max_priority)) = (max_fee_per_gas, max_priority_fee_per_gas) {
610 let capped_max_fee = Self::cap_gas_price(max_fee, gas_price_cap);
612
613 let capped_max_priority = Self::cap_gas_price(max_priority, capped_max_fee);
615 Ok((None, Some(capped_max_fee), Some(capped_max_priority)))
616 } else {
617 Ok((
619 Some(Self::cap_gas_price(gas_price, gas_price_cap)),
620 None,
621 None,
622 ))
623 }
624 }
625
626 fn cap_gas_price(price: u128, cap: u128) -> u128 {
627 std::cmp::min(price, cap)
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_int = MINUTE_AND_HALF_MS / block_interval_ms;
656
657 let n_blocks_frac = ((MINUTE_AND_HALF_MS % block_interval_ms) * 1000) / block_interval_ms;
659
660 let mut multiplier = PRECISION;
663
664 for _ in 0..n_blocks_int {
666 multiplier = (multiplier
667 * (PRECISION + (PRECISION * BASE_FEE_INCREASE_FACTOR_PERCENT) / 1000))
668 / PRECISION;
669 }
670
671 if n_blocks_frac > 0 {
674 let frac_increase =
675 (n_blocks_frac * BASE_FEE_INCREASE_FACTOR_PERCENT * PRECISION) / (1000 * 1000);
676 multiplier = (multiplier * (PRECISION + frac_increase)) / PRECISION;
677 }
678
679 std::cmp::min(multiplier, MAX_BASE_FEE_MULTIPLIER)
681}
682
683fn calculate_max_fee_per_gas(
685 base_fee_wei: u128,
686 max_priority_fee_wei: u128,
687 network: &EvmNetwork,
688) -> u128 {
689 let multiplier = get_base_fee_multiplier(network);
691
692 let multiplied_base_fee = (base_fee_wei * multiplier) / PRECISION;
694
695 multiplied_base_fee + max_priority_fee_wei
697}
698#[cfg(test)]
699mod tests {
700 use super::*;
701 use crate::models::{
702 evm::Speed, EvmNetwork, EvmTransactionData, NetworkType, RelayerEvmPolicy,
703 RelayerNetworkPolicy, RelayerRepoModel, U256,
704 };
705 use crate::services::{
706 EvmGasPriceService, GasPrices, MockEvmGasPriceServiceTrait, MockEvmProviderTrait,
707 SpeedPrices,
708 };
709 use futures::FutureExt;
710
711 fn create_mock_evm_network(name: &str) -> EvmNetwork {
712 let average_blocktime_ms = match name {
713 "optimism" => 2000, _ => 12000, };
716
717 EvmNetwork {
718 network: name.to_string(),
719 rpc_urls: vec!["https://rpc.example.com".to_string()],
720 explorer_urls: None,
721 average_blocktime_ms,
722 is_testnet: true,
723 tags: vec![],
724 chain_id: 1337,
725 required_confirmations: 1,
726 features: vec![],
727 symbol: "ETH".to_string(),
728 }
729 }
730
731 fn create_mock_no_mempool_network(name: &str) -> EvmNetwork {
732 let average_blocktime_ms = match name {
733 "arbitrum" => 1000, _ => 12000, };
736
737 EvmNetwork {
738 network: name.to_string(),
739 rpc_urls: vec!["https://rpc.example.com".to_string()],
740 explorer_urls: None,
741 average_blocktime_ms,
742 is_testnet: true,
743 tags: vec!["no-mempool".to_string()], chain_id: 42161,
745 required_confirmations: 1,
746 features: vec!["eip1559".to_string()], symbol: "ETH".to_string(),
748 }
749 }
750
751 fn create_mock_relayer() -> RelayerRepoModel {
752 RelayerRepoModel {
753 id: "test-relayer".to_string(),
754 name: "Test Relayer".to_string(),
755 network: "mainnet".to_string(),
756 network_type: NetworkType::Evm,
757 address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string(),
758 policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy::default()),
759 paused: false,
760 notification_id: None,
761 signer_id: "test-signer".to_string(),
762 system_disabled: false,
763 custom_rpc_urls: None,
764 }
765 }
766
767 #[tokio::test]
768 async fn test_legacy_transaction() {
769 let mut provider = MockEvmProviderTrait::new();
770 provider
771 .expect_get_balance()
772 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
773
774 let relayer = create_mock_relayer();
775 let gas_price_service =
776 EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"));
777
778 let tx_data = EvmTransactionData {
779 gas_price: Some(20000000000),
780 ..Default::default()
781 };
782
783 let mut provider = MockEvmProviderTrait::new();
784 provider
785 .expect_get_balance()
786 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
787
788 let pc = PriceCalculator::new(gas_price_service, NetworkExtraFeeCalculator::None);
790
791 let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
792 assert!(result.is_ok());
793 let params = result.unwrap();
794 assert_eq!(params.gas_price, Some(20000000000));
795 assert!(params.max_fee_per_gas.is_none());
796 assert!(params.max_priority_fee_per_gas.is_none());
797 }
798
799 #[tokio::test]
800 async fn test_eip1559_transaction() {
801 let mut provider = MockEvmProviderTrait::new();
802 provider
803 .expect_get_balance()
804 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
805
806 let relayer = create_mock_relayer();
807 let gas_price_service =
808 EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"));
809
810 let tx_data = EvmTransactionData {
811 gas_price: None,
812 max_fee_per_gas: Some(30000000000),
813 max_priority_fee_per_gas: Some(2000000000),
814 ..Default::default()
815 };
816
817 let mut provider = MockEvmProviderTrait::new();
818 provider
819 .expect_get_balance()
820 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
821
822 let pc = PriceCalculator::new(gas_price_service, NetworkExtraFeeCalculator::None);
824
825 let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
826 assert!(result.is_ok());
827 let params = result.unwrap();
828 assert!(params.gas_price.is_none());
829 assert_eq!(params.max_fee_per_gas, Some(30000000000));
830 assert_eq!(params.max_priority_fee_per_gas, Some(2000000000));
831 }
832
833 #[tokio::test]
834 async fn test_speed_legacy_based_transaction() {
835 let mut provider = MockEvmProviderTrait::new();
836 provider
837 .expect_get_balance()
838 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
839 provider
840 .expect_get_gas_price()
841 .returning(|| async { Ok(20000000000) }.boxed());
842
843 let relayer = create_mock_relayer();
844 let gas_price_service = EvmGasPriceService::new(provider, create_mock_evm_network("celo"));
845
846 let tx_data = EvmTransactionData {
847 gas_price: None,
848 speed: Some(Speed::Fast),
849 ..Default::default()
850 };
851
852 let mut provider = MockEvmProviderTrait::new();
853 provider
854 .expect_get_balance()
855 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
856 provider
857 .expect_get_gas_price()
858 .returning(|| async { Ok(20000000000) }.boxed());
859
860 let pc = PriceCalculator::new(gas_price_service, NetworkExtraFeeCalculator::None);
861
862 let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
863 assert!(result.is_ok());
864 let params = result.unwrap();
865 assert!(
866 params.gas_price.is_some()
867 || (params.max_fee_per_gas.is_some() && params.max_priority_fee_per_gas.is_some())
868 );
869 }
870
871 #[tokio::test]
872 async fn test_invalid_transaction_type() {
873 let mut provider = MockEvmProviderTrait::new();
874 provider
875 .expect_get_balance()
876 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
877
878 let relayer = create_mock_relayer();
879 let gas_price_service =
880 EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"));
881
882 let tx_data = EvmTransactionData {
883 gas_price: None,
884 ..Default::default()
885 };
886
887 let mut provider = MockEvmProviderTrait::new();
888 provider
889 .expect_get_balance()
890 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
891
892 let pc = PriceCalculator::new(gas_price_service, NetworkExtraFeeCalculator::None);
893
894 let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
895 assert!(result.is_err());
896 assert!(matches!(
897 result.unwrap_err(),
898 TransactionError::NotSupported(_)
899 ));
900 }
901
902 #[tokio::test]
903 async fn test_gas_price_cap() {
904 let mut provider = MockEvmProviderTrait::new();
905 provider
906 .expect_get_balance()
907 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
908
909 let mut relayer = create_mock_relayer();
910 let gas_price_service =
911 EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"));
912
913 let evm_policy = RelayerEvmPolicy {
915 gas_price_cap: Some(10000000000),
916 eip1559_pricing: Some(true),
917 ..RelayerEvmPolicy::default()
918 };
919 relayer.policies = RelayerNetworkPolicy::Evm(evm_policy);
920
921 let tx_data = EvmTransactionData {
922 gas_price: Some(20000000000), ..Default::default()
924 };
925
926 let mut provider = MockEvmProviderTrait::new();
927 provider
928 .expect_get_balance()
929 .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
930
931 let pc = PriceCalculator::new(gas_price_service, NetworkExtraFeeCalculator::None);
932
933 let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
934 assert!(result.is_ok());
935 let params = result.unwrap();
936 assert_eq!(params.gas_price, Some(10000000000)); }
938
939 #[test]
940 fn test_get_base_fee_multiplier() {
941 let mainnet = create_mock_evm_network("mainnet");
942 let multiplier = super::get_base_fee_multiplier(&mainnet);
943 assert!(multiplier > 2_300_000_000 && multiplier < 2_500_000_000);
945
946 let optimism = create_mock_evm_network("optimism");
947 let multiplier = super::get_base_fee_multiplier(&optimism);
948 assert_eq!(multiplier, MAX_BASE_FEE_MULTIPLIER);
950 }
951
952 #[test]
953 fn test_calculate_max_fee_per_gas() {
954 let network = create_mock_evm_network("mainnet");
955 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);
959 println!("max_fee: {:?}", max_fee);
960 assert!(max_fee > 240_000_000_000 && max_fee < 245_000_000_000);
963 }
964
965 #[tokio::test]
966 async fn test_handle_eip1559_speed() {
967 let mut mock_gas_price_service = MockEvmGasPriceServiceTrait::new();
968
969 let test_data = [
971 (Speed::SafeLow, 1_000_000_000),
972 (Speed::Average, 2_000_000_000),
973 (Speed::Fast, 3_000_000_000),
974 (Speed::Fastest, 4_000_000_000),
975 ];
976 let mock_prices = GasPrices {
978 legacy_prices: SpeedPrices {
979 safe_low: 10_000_000_000,
980 average: 12_500_000_000,
981 fast: 15_000_000_000,
982 fastest: 20_000_000_000,
983 },
984 max_priority_fee_per_gas: SpeedPrices {
985 safe_low: 1_000_000_000,
986 average: 2_000_000_000,
987 fast: 3_000_000_000,
988 fastest: 4_000_000_000,
989 },
990 base_fee_per_gas: 50_000_000_000,
991 };
992
993 mock_gas_price_service
995 .expect_get_prices_from_json_rpc()
996 .returning(move || {
997 let prices = mock_prices.clone();
998 Box::pin(async move { Ok(prices) })
999 });
1000
1001 let network = create_mock_evm_network("mainnet");
1003 mock_gas_price_service
1004 .expect_network()
1005 .return_const(network);
1006
1007 let pc = PriceCalculator::new(mock_gas_price_service, NetworkExtraFeeCalculator::None);
1009
1010 for (speed, expected_priority_fee) in test_data {
1011 let result = pc.fetch_eip1559_speed_params(&speed).await;
1013 assert!(result.is_ok());
1014 let params = result.unwrap();
1015 assert_eq!(params.max_priority_fee_per_gas, Some(expected_priority_fee));
1017
1018 let max_fee = params.max_fee_per_gas.unwrap();
1022 let expected_base_portion = 120_000_000_000; assert!(max_fee < expected_base_portion + expected_priority_fee + 2_000_000_000);
1024 }
1025 }
1026
1027 #[tokio::test]
1028 async fn test_calculate_bumped_gas_price_eip1559_basic() {
1029 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1030 let mock_prices = GasPrices {
1031 legacy_prices: SpeedPrices {
1032 safe_low: 8_000_000_000,
1033 average: 10_000_000_000,
1034 fast: 12_000_000_000,
1035 fastest: 15_000_000_000,
1036 },
1037 max_priority_fee_per_gas: SpeedPrices {
1038 safe_low: 1_000_000_000,
1039 average: 2_000_000_000,
1040 fast: 3_000_000_000,
1041 fastest: 4_000_000_000,
1042 },
1043 base_fee_per_gas: 50_000_000_000,
1044 };
1045 mock_service
1046 .expect_get_prices_from_json_rpc()
1047 .returning(move || {
1048 let prices = mock_prices.clone();
1049 Box::pin(async move { Ok(prices) })
1050 });
1051 mock_service
1052 .expect_network()
1053 .return_const(create_mock_evm_network("mainnet"));
1054
1055 let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1056 let mut relayer = create_mock_relayer();
1057 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1059 gas_price_cap: Some(300_000_000_000u128),
1060 ..Default::default()
1061 });
1062
1063 let tx_data = EvmTransactionData {
1064 max_fee_per_gas: Some(100_000_000_000),
1065 max_priority_fee_per_gas: Some(2_000_000_000),
1066 speed: Some(Speed::Fast),
1067 ..Default::default()
1068 };
1069
1070 let bumped = pc
1071 .calculate_bumped_gas_price(&tx_data, &relayer)
1072 .await
1073 .unwrap();
1074 assert!(bumped.max_fee_per_gas.unwrap() >= 110_000_000_000); assert!(bumped.max_priority_fee_per_gas.unwrap() >= 2_200_000_000); }
1077
1078 #[tokio::test]
1079 async fn test_calculate_bumped_gas_price_eip1559_market_lower_than_min_bump() {
1080 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1081 let mock_prices = GasPrices {
1082 legacy_prices: SpeedPrices::default(),
1083 max_priority_fee_per_gas: SpeedPrices {
1084 safe_low: 1_500_000_000, average: 2_500_000_000,
1086 fast: 2_700_000_000,
1087 fastest: 3_000_000_000,
1088 },
1089 base_fee_per_gas: 30_000_000_000,
1090 };
1091 mock_service
1092 .expect_get_prices_from_json_rpc()
1093 .returning(move || {
1094 let prices = mock_prices.clone();
1095 Box::pin(async move { Ok(prices) })
1096 });
1097 mock_service
1098 .expect_network()
1099 .return_const(create_mock_evm_network("mainnet"));
1100
1101 let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1102 let relayer = create_mock_relayer();
1103
1104 let tx_data = EvmTransactionData {
1107 max_fee_per_gas: Some(20_000_000_000),
1108 max_priority_fee_per_gas: Some(2_000_000_000),
1109 speed: Some(Speed::SafeLow),
1110 ..Default::default()
1111 };
1112
1113 let bumped = pc
1114 .calculate_bumped_gas_price(&tx_data, &relayer)
1115 .await
1116 .unwrap();
1117 assert!(bumped.max_priority_fee_per_gas.unwrap() >= 2_200_000_000);
1118 assert!(bumped.max_fee_per_gas.unwrap() > 20_000_000_000);
1119 }
1120
1121 #[tokio::test]
1122 async fn test_calculate_bumped_gas_price_legacy_basic() {
1123 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1124 let mock_prices = GasPrices {
1125 legacy_prices: SpeedPrices {
1126 safe_low: 10_000_000_000,
1127 average: 12_000_000_000,
1128 fast: 14_000_000_000,
1129 fastest: 18_000_000_000,
1130 },
1131 max_priority_fee_per_gas: SpeedPrices::default(),
1132 base_fee_per_gas: 0,
1133 };
1134 mock_service
1135 .expect_get_prices_from_json_rpc()
1136 .returning(move || {
1137 let prices = mock_prices.clone();
1138 Box::pin(async move { Ok(prices) })
1139 });
1140 mock_service
1141 .expect_network()
1142 .return_const(create_mock_evm_network("mainnet"));
1143
1144 let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1145 let relayer = create_mock_relayer();
1146 let tx_data = EvmTransactionData {
1147 gas_price: Some(10_000_000_000),
1148 speed: Some(Speed::Fast),
1149 ..Default::default()
1150 };
1151
1152 let bumped = pc
1153 .calculate_bumped_gas_price(&tx_data, &relayer)
1154 .await
1155 .unwrap();
1156 assert!(bumped.gas_price.unwrap() >= 11_000_000_000); }
1158
1159 #[tokio::test]
1160 async fn test_calculate_bumped_gas_price_missing_params() {
1161 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1162
1163 mock_service
1165 .expect_get_prices_from_json_rpc()
1166 .times(1)
1167 .returning(|| Box::pin(async { Ok(GasPrices::default()) }));
1168
1169 mock_service
1171 .expect_network()
1172 .return_const(create_mock_evm_network("mainnet"));
1173
1174 let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1175 let relayer = create_mock_relayer();
1176 let tx_data = EvmTransactionData {
1178 gas_price: None,
1179 max_fee_per_gas: None,
1180 max_priority_fee_per_gas: None,
1181 ..Default::default()
1182 };
1183
1184 let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1185 assert!(result.is_err());
1186 if let Err(TransactionError::InvalidType(msg)) = result {
1187 assert!(msg.contains("missing required gas price parameters"));
1188 } else {
1189 panic!("Expected InvalidType error");
1190 }
1191 }
1192
1193 #[tokio::test]
1194 async fn test_calculate_bumped_gas_price_capped() {
1195 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1196 let mock_prices = GasPrices {
1197 legacy_prices: SpeedPrices::default(),
1198 max_priority_fee_per_gas: SpeedPrices {
1199 safe_low: 4_000_000_000,
1200 average: 5_000_000_000,
1201 fast: 6_000_000_000,
1202 fastest: 8_000_000_000,
1203 },
1204 base_fee_per_gas: 100_000_000_000,
1205 };
1206 mock_service
1207 .expect_get_prices_from_json_rpc()
1208 .returning(move || {
1209 let prices = mock_prices.clone();
1210 Box::pin(async move { Ok(prices) })
1211 });
1212 mock_service
1213 .expect_network()
1214 .return_const(create_mock_evm_network("mainnet"));
1215
1216 let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1217 let mut relayer = create_mock_relayer();
1218 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1219 gas_price_cap: Some(105_000_000_000),
1220 ..Default::default()
1221 });
1222
1223 let tx_data = EvmTransactionData {
1224 max_fee_per_gas: Some(90_000_000_000),
1225 max_priority_fee_per_gas: Some(4_000_000_000),
1226 speed: Some(Speed::Fastest),
1227 ..Default::default()
1228 };
1229
1230 let bumped = pc
1232 .calculate_bumped_gas_price(&tx_data, &relayer)
1233 .await
1234 .unwrap();
1235 assert!(bumped.max_fee_per_gas.unwrap() <= 105_000_000_000);
1236 assert!(bumped.max_priority_fee_per_gas.unwrap() <= 105_000_000_000);
1237 }
1238
1239 #[tokio::test]
1240 async fn test_is_min_bumped_flag_eip1559() {
1241 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1242 let mock_prices = GasPrices {
1243 legacy_prices: SpeedPrices::default(),
1244 max_priority_fee_per_gas: SpeedPrices {
1245 safe_low: 1_000_000_000,
1246 average: 2_000_000_000,
1247 fast: 3_000_000_000,
1248 fastest: 4_000_000_000,
1249 },
1250 base_fee_per_gas: 40_000_000_000,
1251 };
1252 mock_service
1253 .expect_get_prices_from_json_rpc()
1254 .returning(move || {
1255 let prices = mock_prices.clone();
1256 Box::pin(async move { Ok(prices) })
1257 });
1258 mock_service
1259 .expect_network()
1260 .return_const(create_mock_evm_network("mainnet"));
1261
1262 let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1263 let mut relayer = create_mock_relayer();
1264
1265 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1267 gas_price_cap: Some(200_000_000_000u128),
1268 ..Default::default()
1269 });
1270
1271 let tx_data = EvmTransactionData {
1272 max_fee_per_gas: Some(50_000_000_000),
1273 max_priority_fee_per_gas: Some(2_000_000_000),
1274 speed: Some(Speed::Fast),
1275 ..Default::default()
1276 };
1277
1278 let bumped = pc
1279 .calculate_bumped_gas_price(&tx_data, &relayer)
1280 .await
1281 .unwrap();
1282 assert_eq!(
1283 bumped.is_min_bumped,
1284 Some(true),
1285 "Should be min bumped when prices are high enough"
1286 );
1287
1288 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1290 gas_price_cap: Some(50_000_000_000u128), ..Default::default()
1292 });
1293
1294 let tx_data = EvmTransactionData {
1295 max_fee_per_gas: Some(50_000_000_000),
1296 max_priority_fee_per_gas: Some(2_000_000_000),
1297 speed: Some(Speed::Fast),
1298 ..Default::default()
1299 };
1300
1301 let bumped = pc
1302 .calculate_bumped_gas_price(&tx_data, &relayer)
1303 .await
1304 .unwrap();
1305 assert_eq!(
1307 bumped.is_min_bumped,
1308 Some(false),
1309 "Should not be min bumped when cap is too low"
1310 );
1311 }
1312
1313 #[tokio::test]
1314 async fn test_is_min_bumped_flag_legacy() {
1315 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1316 let mock_prices = GasPrices {
1317 legacy_prices: SpeedPrices {
1318 safe_low: 8_000_000_000,
1319 average: 10_000_000_000,
1320 fast: 12_000_000_000,
1321 fastest: 15_000_000_000,
1322 },
1323 max_priority_fee_per_gas: SpeedPrices::default(),
1324 base_fee_per_gas: 0,
1325 };
1326 mock_service
1327 .expect_get_prices_from_json_rpc()
1328 .returning(move || {
1329 let prices = mock_prices.clone();
1330 Box::pin(async move { Ok(prices) })
1331 });
1332 mock_service
1333 .expect_network()
1334 .return_const(create_mock_evm_network("mainnet"));
1335
1336 let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1337 let mut relayer = create_mock_relayer();
1338
1339 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1341 gas_price_cap: Some(100_000_000_000u128),
1342 ..Default::default()
1343 });
1344
1345 let tx_data = EvmTransactionData {
1346 gas_price: Some(10_000_000_000),
1347 speed: Some(Speed::Fast),
1348 ..Default::default()
1349 };
1350
1351 let bumped = pc
1352 .calculate_bumped_gas_price(&tx_data, &relayer)
1353 .await
1354 .unwrap();
1355 assert_eq!(
1356 bumped.is_min_bumped,
1357 Some(true),
1358 "Should be min bumped with sufficient cap"
1359 );
1360
1361 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1363 gas_price_cap: Some(10_000_000_000u128), ..Default::default()
1365 });
1366
1367 let bumped = pc
1368 .calculate_bumped_gas_price(&tx_data, &relayer)
1369 .await
1370 .unwrap();
1371 assert_eq!(
1372 bumped.is_min_bumped,
1373 Some(false),
1374 "Should not be min bumped with insufficient cap"
1375 );
1376 }
1377
1378 #[tokio::test]
1379 async fn test_calculate_bumped_gas_price_with_extra_fee() {
1380 let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
1382 mock_gas_service
1383 .expect_get_prices_from_json_rpc()
1384 .returning(|| {
1385 Box::pin(async {
1386 Ok(GasPrices {
1387 legacy_prices: SpeedPrices {
1388 safe_low: 10_000_000_000,
1389 average: 12_000_000_000,
1390 fast: 14_000_000_000,
1391 fastest: 18_000_000_000,
1392 },
1393 max_priority_fee_per_gas: SpeedPrices::default(),
1394 base_fee_per_gas: 40_000_000_000,
1395 })
1396 })
1397 });
1398 mock_gas_service
1399 .expect_network()
1400 .return_const(create_mock_evm_network("mainnet"));
1401
1402 let pc = PriceCalculator::new(mock_gas_service, NetworkExtraFeeCalculator::None);
1404
1405 let relayer = create_mock_relayer();
1407 let tx_data = EvmTransactionData {
1408 max_fee_per_gas: Some(50_000_000_000),
1409 max_priority_fee_per_gas: Some(2_000_000_000),
1410 speed: Some(Speed::Fast),
1411 ..Default::default()
1412 };
1413
1414 let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1416
1417 assert!(result.is_ok());
1419 let price_params = result.unwrap();
1420 assert_eq!(price_params.extra_fee, None);
1421 }
1422
1423 #[tokio::test]
1424 async fn test_calculate_bumped_gas_price_with_mock_extra_fee() {
1425 let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
1427 mock_gas_service
1428 .expect_get_prices_from_json_rpc()
1429 .returning(|| {
1430 Box::pin(async {
1431 Ok(GasPrices {
1432 legacy_prices: SpeedPrices {
1433 safe_low: 10_000_000_000,
1434 average: 12_000_000_000,
1435 fast: 14_000_000_000,
1436 fastest: 18_000_000_000,
1437 },
1438 max_priority_fee_per_gas: SpeedPrices::default(),
1439 base_fee_per_gas: 40_000_000_000,
1440 })
1441 })
1442 });
1443 mock_gas_service
1444 .expect_network()
1445 .return_const(create_mock_evm_network("mainnet"));
1446
1447 let mut mock_extra_fee_service = MockNetworkExtraFeeCalculatorServiceTrait::new();
1449
1450 let expected_extra_fee = U256::from(123456789u64);
1452 mock_extra_fee_service
1453 .expect_get_extra_fee()
1454 .returning(move |_| Box::pin(async move { Ok(expected_extra_fee) }));
1455
1456 let extra_fee_calculator = NetworkExtraFeeCalculator::Mock(mock_extra_fee_service);
1458
1459 let pc = PriceCalculator::new(mock_gas_service, extra_fee_calculator);
1461
1462 let relayer = create_mock_relayer();
1464 let tx_data = EvmTransactionData {
1465 max_fee_per_gas: Some(50_000_000_000),
1466 max_priority_fee_per_gas: Some(2_000_000_000),
1467 speed: Some(Speed::Fast),
1468 ..Default::default()
1469 };
1470
1471 let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1473
1474 assert!(result.is_ok());
1476 let price_params = result.unwrap();
1477 assert_eq!(
1478 price_params.extra_fee,
1479 Some(expected_extra_fee.try_into().unwrap_or(0))
1480 );
1481 }
1482
1483 #[test]
1484 fn test_calculate_total_cost_eip1559() {
1485 let price_params = PriceParams {
1486 gas_price: None,
1487 max_fee_per_gas: Some(30_000_000_000),
1488 max_priority_fee_per_gas: Some(2_000_000_000),
1489 is_min_bumped: None,
1490 extra_fee: None,
1491 total_cost: U256::ZERO,
1492 };
1493
1494 let gas_limit = 100_000;
1495 let value = U256::from(1_000_000_000_000_000_000u128); let is_eip1559 = true;
1497
1498 let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1499
1500 let expected = U256::from(30_000_000_000u128) * U256::from(gas_limit) + value;
1502 assert_eq!(total_cost, expected);
1503 }
1504
1505 #[test]
1506 fn test_calculate_total_cost_legacy() {
1507 let price_params = PriceParams {
1508 gas_price: Some(20_000_000_000),
1509 max_fee_per_gas: None,
1510 max_priority_fee_per_gas: None,
1511 is_min_bumped: None,
1512 extra_fee: None,
1513 total_cost: U256::ZERO,
1514 };
1515
1516 let gas_limit = 100_000;
1517 let value = U256::from(1_000_000_000_000_000_000u128); let is_eip1559 = false;
1519
1520 let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1521
1522 let expected = U256::from(20_000_000_000u128) * U256::from(gas_limit) + value;
1524 assert_eq!(total_cost, expected);
1525 }
1526
1527 #[test]
1528 fn test_calculate_total_cost_with_extra_fee() {
1529 let price_params = PriceParams {
1530 gas_price: Some(20_000_000_000),
1531 max_fee_per_gas: None,
1532 max_priority_fee_per_gas: None,
1533 is_min_bumped: None,
1534 extra_fee: Some(5_000_000_000),
1535 total_cost: U256::ZERO,
1536 };
1537
1538 let gas_limit = 100_000;
1539 let value = U256::from(1_000_000_000_000_000_000u128); let is_eip1559 = false;
1541
1542 let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1543
1544 let expected = U256::from(20_000_000_000u128) * U256::from(gas_limit)
1545 + value
1546 + U256::from(5_000_000_000u128);
1547 assert_eq!(total_cost, expected);
1548 }
1549
1550 #[test]
1551 fn test_calculate_total_cost_zero_values() {
1552 let price_params = PriceParams {
1553 gas_price: Some(0),
1554 max_fee_per_gas: Some(0),
1555 max_priority_fee_per_gas: Some(0),
1556 is_min_bumped: None,
1557 extra_fee: Some(0),
1558 total_cost: U256::ZERO,
1559 };
1560
1561 let gas_limit = 0;
1562 let value = U256::from(0);
1563
1564 let legacy_total_cost = price_params.calculate_total_cost(false, gas_limit, value);
1565 assert_eq!(legacy_total_cost, U256::ZERO);
1566
1567 let eip1559_total_cost = price_params.calculate_total_cost(true, gas_limit, value);
1568 assert_eq!(eip1559_total_cost, U256::ZERO);
1569 }
1570
1571 #[test]
1572 fn test_calculate_total_cost_missing_values() {
1573 let price_params = PriceParams {
1574 gas_price: None,
1575 max_fee_per_gas: None,
1576 max_priority_fee_per_gas: None,
1577 is_min_bumped: None,
1578 extra_fee: None,
1579 total_cost: U256::ZERO,
1580 };
1581
1582 let gas_limit = 100_000;
1583 let value = U256::from(1_000_000_000_000_000_000u128);
1584
1585 let legacy_total = price_params.calculate_total_cost(false, gas_limit, value);
1586 assert_eq!(legacy_total, value);
1587
1588 let eip1559_total = price_params.calculate_total_cost(true, gas_limit, value);
1589 assert_eq!(eip1559_total, value);
1590 }
1591
1592 #[test]
1593 fn test_calculate_min_bump_normal_cases() {
1594 let base_price = 20_000_000_000u128; let expected = 22_000_000_000u128; assert_eq!(calculate_min_bump(base_price), expected);
1597
1598 let base_price = 1_000_000_000u128;
1599 let expected = 1_100_000_000u128; assert_eq!(calculate_min_bump(base_price), expected);
1601
1602 let base_price = 100_000_000_000u128;
1603 let expected = 110_000_000_000u128; assert_eq!(calculate_min_bump(base_price), expected);
1605 }
1606
1607 #[test]
1608 fn test_calculate_min_bump_edge_cases() {
1609 assert_eq!(calculate_min_bump(0), 1);
1611
1612 let result = calculate_min_bump(1);
1614 assert!(result >= 2);
1615
1616 let base_price = 5u128; let result = calculate_min_bump(base_price);
1619 assert!(
1620 result > base_price,
1621 "Result {} should be greater than base_price {}",
1622 result,
1623 base_price
1624 );
1625
1626 let base_price = 9u128;
1627 let result = calculate_min_bump(base_price);
1628 assert_eq!(
1629 result, 10u128,
1630 "9 wei should bump to 10 wei (minimum 1 wei increase)"
1631 );
1632 }
1633
1634 #[test]
1635 fn test_calculate_min_bump_large_values() {
1636 let base_price = u128::MAX / 2;
1638 let result = calculate_min_bump(base_price);
1639 assert!(result > base_price);
1640
1641 let base_price = u128::MAX - 1000;
1643 let result = calculate_min_bump(base_price);
1644 assert!(result >= base_price.saturating_add(1));
1646 }
1647
1648 #[test]
1649 fn test_calculate_min_bump_overflow_protection() {
1650 let base_price = u128::MAX;
1651 let result = calculate_min_bump(base_price);
1652 assert_eq!(result, u128::MAX);
1653
1654 let base_price = (u128::MAX / 11) * 10 + 1;
1655 let result = calculate_min_bump(base_price);
1656 assert!(result >= base_price);
1657 }
1658
1659 #[test]
1660 fn test_calculate_min_bump_minimum_increase_guarantee() {
1661 let test_cases = vec![0, 1, 2, 5, 9, 10, 100, 1000, 10000];
1663
1664 for base_price in test_cases {
1665 let result = calculate_min_bump(base_price);
1666 assert!(
1667 result > base_price,
1668 "calculate_min_bump({}) = {} should be greater than base_price",
1669 base_price,
1670 result
1671 );
1672 assert!(
1673 result >= base_price.saturating_add(1),
1674 "calculate_min_bump({}) = {} should be at least base_price + 1",
1675 base_price,
1676 result
1677 );
1678 }
1679 }
1680
1681 #[tokio::test]
1682 async fn test_calculate_bumped_gas_price_no_mempool_network_eip1559() {
1683 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1684
1685 mock_service
1687 .expect_network()
1688 .return_const(create_mock_no_mempool_network("arbitrum"));
1689
1690 let mock_prices = GasPrices {
1692 legacy_prices: SpeedPrices {
1693 safe_low: 1_000_000_000,
1694 average: 2_000_000_000,
1695 fast: 3_000_000_000,
1696 fastest: 4_000_000_000,
1697 },
1698 max_priority_fee_per_gas: SpeedPrices {
1699 safe_low: 1_000_000_000,
1700 average: 2_000_000_000,
1701 fast: 3_000_000_000,
1702 fastest: 4_000_000_000,
1703 },
1704 base_fee_per_gas: 50_000_000_000,
1705 };
1706
1707 mock_service
1708 .expect_get_prices_from_json_rpc()
1709 .returning(move || {
1710 let prices = mock_prices.clone();
1711 Box::pin(async move { Ok(prices) })
1712 });
1713
1714 mock_service
1716 .expect_get_legacy_prices_from_json_rpc()
1717 .returning(|| {
1718 Box::pin(async {
1719 Ok(SpeedPrices {
1720 safe_low: 1_000_000_000,
1721 average: 2_000_000_000,
1722 fast: 3_000_000_000,
1723 fastest: 4_000_000_000,
1724 })
1725 })
1726 });
1727
1728 let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1729 let mut relayer = create_mock_relayer();
1730
1731 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1733 eip1559_pricing: Some(true),
1734 ..Default::default()
1735 });
1736
1737 let tx_data = EvmTransactionData {
1739 speed: Some(Speed::Fast),
1740 gas_limit: Some(21000),
1741 value: U256::from(1_000_000_000_000_000_000u128), ..Default::default()
1743 };
1744
1745 let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1746
1747 assert!(result.is_ok());
1748 let price_params = result.unwrap();
1749
1750 assert_eq!(price_params.is_min_bumped, Some(true));
1752
1753 assert!(
1759 price_params.max_priority_fee_per_gas.is_some() || price_params.gas_price.is_some()
1760 );
1761
1762 assert!(price_params.total_cost > U256::ZERO);
1764 }
1765
1766 #[tokio::test]
1767 async fn test_calculate_bumped_gas_price_no_mempool_network_legacy() {
1768 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1769
1770 mock_service
1772 .expect_network()
1773 .return_const(create_mock_no_mempool_network("arbitrum"));
1774
1775 let mock_legacy_prices = SpeedPrices {
1777 safe_low: 10_000_000_000,
1778 average: 12_000_000_000,
1779 fast: 14_000_000_000,
1780 fastest: 18_000_000_000,
1781 };
1782
1783 mock_service
1784 .expect_get_legacy_prices_from_json_rpc()
1785 .returning(move || {
1786 let prices = mock_legacy_prices.clone();
1787 Box::pin(async move { Ok(prices) })
1788 });
1789
1790 let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1791 let mut relayer = create_mock_relayer();
1792
1793 relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1795 eip1559_pricing: Some(false),
1796 ..Default::default()
1797 });
1798
1799 let tx_data = EvmTransactionData {
1801 speed: Some(Speed::Fast),
1802 gas_limit: Some(21000),
1803 value: U256::from(1_000_000_000_000_000_000u128), ..Default::default()
1805 };
1806
1807 let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1808
1809 assert!(result.is_ok());
1810 let price_params = result.unwrap();
1811
1812 assert_eq!(price_params.is_min_bumped, Some(true));
1814
1815 assert!(
1817 price_params.max_priority_fee_per_gas.is_some() || price_params.gas_price.is_some()
1818 );
1819
1820 assert!(price_params.total_cost > U256::ZERO);
1822 }
1823
1824 #[tokio::test]
1825 async fn test_calculate_bumped_gas_price_no_mempool_vs_regular_network() {
1826 let mut mock_service_regular = MockEvmGasPriceServiceTrait::new();
1828
1829 let mock_prices = GasPrices {
1830 legacy_prices: SpeedPrices::default(),
1831 max_priority_fee_per_gas: SpeedPrices {
1832 safe_low: 1_000_000_000,
1833 average: 2_000_000_000,
1834 fast: 3_000_000_000,
1835 fastest: 4_000_000_000,
1836 },
1837 base_fee_per_gas: 50_000_000_000,
1838 };
1839
1840 mock_service_regular
1841 .expect_network()
1842 .return_const(create_mock_evm_network("mainnet"));
1843
1844 let mock_prices_clone = mock_prices.clone();
1845 mock_service_regular
1846 .expect_get_prices_from_json_rpc()
1847 .returning(move || {
1848 let prices = mock_prices_clone.clone();
1849 Box::pin(async move { Ok(prices) })
1850 });
1851
1852 let pc_regular =
1853 PriceCalculator::new(mock_service_regular, NetworkExtraFeeCalculator::None);
1854 let relayer = create_mock_relayer();
1855
1856 let tx_data = EvmTransactionData {
1857 speed: Some(Speed::Fast),
1858 ..Default::default()
1859 };
1860
1861 let result_regular = pc_regular
1862 .calculate_bumped_gas_price(&tx_data, &relayer)
1863 .await
1864 .unwrap();
1865
1866 assert!(
1868 result_regular.max_priority_fee_per_gas.is_some() || result_regular.gas_price.is_some()
1869 );
1870
1871 let mut mock_service_no_mempool = MockEvmGasPriceServiceTrait::new();
1873
1874 mock_service_no_mempool
1875 .expect_network()
1876 .return_const(create_mock_no_mempool_network("arbitrum"));
1877
1878 mock_service_no_mempool
1879 .expect_get_prices_from_json_rpc()
1880 .returning(move || {
1881 let prices = mock_prices.clone();
1882 Box::pin(async move { Ok(prices) })
1883 });
1884
1885 let pc_no_mempool =
1886 PriceCalculator::new(mock_service_no_mempool, NetworkExtraFeeCalculator::None);
1887
1888 let result_no_mempool = pc_no_mempool
1889 .calculate_bumped_gas_price(&tx_data, &relayer)
1890 .await
1891 .unwrap();
1892
1893 assert_eq!(result_no_mempool.is_min_bumped, Some(true));
1895
1896 assert!(
1898 result_no_mempool.max_priority_fee_per_gas.is_some()
1899 || result_no_mempool.gas_price.is_some()
1900 );
1901
1902 assert_eq!(result_no_mempool.is_min_bumped, Some(true));
1905 }
1906
1907 #[tokio::test]
1908 async fn test_calculate_bumped_gas_price_arbitrum_network() {
1909 let mut mock_service = MockEvmGasPriceServiceTrait::new();
1910
1911 let arbitrum_network = EvmNetwork {
1913 network: "arbitrum-one".to_string(),
1914 rpc_urls: vec!["https://arb1.arbitrum.io/rpc".to_string()],
1915 explorer_urls: None,
1916 average_blocktime_ms: 1000, is_testnet: false,
1918 tags: vec!["arbitrum-based".to_string()], chain_id: 42161,
1920 required_confirmations: 1,
1921 features: vec!["eip1559".to_string()],
1922 symbol: "ETH".to_string(),
1923 };
1924
1925 mock_service.expect_network().return_const(arbitrum_network);
1927
1928 let mock_prices = GasPrices {
1930 legacy_prices: SpeedPrices {
1931 safe_low: 100_000_000, average: 200_000_000,
1933 fast: 300_000_000,
1934 fastest: 400_000_000,
1935 },
1936 max_priority_fee_per_gas: SpeedPrices {
1937 safe_low: 10_000_000, average: 20_000_000,
1939 fast: 30_000_000,
1940 fastest: 40_000_000,
1941 },
1942 base_fee_per_gas: 100_000_000, };
1944
1945 mock_service
1946 .expect_get_prices_from_json_rpc()
1947 .returning(move || {
1948 let prices = mock_prices.clone();
1949 Box::pin(async move { Ok(prices) })
1950 });
1951
1952 let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1953 let relayer = create_mock_relayer();
1954
1955 let tx_data = EvmTransactionData {
1957 speed: Some(Speed::Fast),
1958 gas_limit: Some(21000),
1959 value: U256::from(1_000_000_000_000_000_000u128), ..Default::default()
1961 };
1962
1963 let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1964
1965 assert!(result.is_ok());
1966 let price_params = result.unwrap();
1967
1968 assert_eq!(
1970 price_params.is_min_bumped,
1971 Some(true),
1972 "Arbitrum networks should skip bumping and use current market prices"
1973 );
1974
1975 assert!(
1977 price_params.max_priority_fee_per_gas.is_some() || price_params.gas_price.is_some(),
1978 "Should return some form of pricing"
1979 );
1980
1981 assert!(
1983 price_params.total_cost > U256::ZERO,
1984 "Should have non-zero total cost"
1985 );
1986
1987 if let Some(priority_fee) = price_params.max_priority_fee_per_gas {
1990 assert!(
1992 priority_fee <= 50_000_000, "Priority fee should be based on current market, not bumped: {}",
1994 priority_fee
1995 );
1996 }
1997 }
1998}