openzeppelin_relayer/domain/transaction/evm/
price_calculator.rs

1//! Gas price calculation module for Ethereum transactions.
2//!
3//! This module provides functionality for calculating gas prices for different types of Ethereum transactions:
4//! - Legacy transactions (using `gas_price`)
5//! - EIP1559 transactions (using `max_fee_per_gas` and `max_priority_fee_per_gas`)
6//! - Speed-based transactions (automatically choosing between legacy and EIP1559 based on network support)
7//!
8//! The module implements various pricing strategies and safety mechanisms:
9//! - Gas price caps to protect against excessive fees
10//! - Dynamic base fee calculations for EIP1559 transactions
11//! - Speed-based multipliers for different transaction priorities (SafeLow, Average, Fast, Fastest)
12//! - Network-specific block time considerations for fee estimations
13//!
14//! # Example
15//! ```rust, ignore
16//! # use your_crate::{PriceCalculator, EvmTransactionData, RelayerRepoModel, EvmGasPriceService};
17//! # async fn example<P: EvmProviderTrait>(
18//! #     tx_data: &EvmTransactionData,
19//! #     relayer: &RelayerRepoModel,
20//! #     gas_price_service: &EvmGasPriceService<P>,
21//! #     provider: &P
22//! # ) -> Result<(), TransactionError> {
23//! let price_params = PriceCalculator::get_transaction_price_params(
24//!     tx_data,
25//!     relayer,
26//!     gas_price_service,
27//!     provider
28//! ).await?;
29//! # Ok(())
30//! # }
31//! ```
32//!
33//! The module uses EIP1559-specific constants for calculating appropriate gas fees:
34//! - Base fee increase factor: 12.5% per block
35//! - Maximum base fee multiplier: 10x
36//! - Time window for fee calculation: 90 seconds
37use 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; // 10^9 (similar to Gwei)
74const MINUTE_AND_HALF_MS: u128 = 90000;
75const BASE_FEE_INCREASE_FACTOR_PERCENT: u128 = 125; // 12.5% increase per block (as percentage * 10)
76const MAX_BASE_FEE_MULTIPLIER: u128 = 10 * PRECISION; // 10.0 * PRECISION
77
78#[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
105/// Safely calculates the minimum required gas price for a replacement transaction.
106/// Uses saturating arithmetic to prevent overflow and maintains precision.
107///
108/// # Arguments
109///
110/// * `base_price` - The original gas price to calculate bump from
111///
112/// # Returns
113///
114/// The minimum required price for replacement, or `u128::MAX` if overflow would occur.
115pub fn calculate_min_bump(base_price: u128) -> u128 {
116    // Convert MIN_BUMP_FACTOR to a rational representation to avoid floating point precision issues
117    // MIN_BUMP_FACTOR = 1.1 = 11/10
118    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    // Ensure we always bump by at least 1 wei to guarantee replacement
126    std::cmp::max(bumped_price, base_price.saturating_add(1))
127}
128
129/// Primary struct for calculating gas prices with an injected `EvmGasPriceServiceTrait`.
130pub 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    /// Calculates transaction price parameters based on the transaction type and network conditions.
166    ///
167    /// This function determines the appropriate gas pricing strategy based on the transaction type:
168    /// - For legacy transactions: calculates gas_price
169    /// - For EIP1559 transactions: calculates max_fee_per_gas and max_priority_fee_per_gas
170    /// - For speed-based transactions: automatically chooses between legacy and EIP1559 based on network support
171    ///
172    /// # Arguments
173    /// * `tx_data` - Transaction data containing type and pricing information
174    /// * `relayer` - Relayer configuration including pricing policies and caps
175    /// * `gas_price_service` - Service for fetching current gas prices from the network
176    /// * `provider` - Network provider for accessing blockchain data
177    ///
178    /// # Returns
179    /// * `Result<PriceParams, TransactionError>` - Calculated price parameters or error
180    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), // Use default gas limit if not provided
220            U256::from(tx_data.value),
221        );
222
223        Ok(final_params)
224    }
225
226    /// Computes bumped gas price for transaction resubmission, factoring in network conditions.
227    ///
228    /// This refactor breaks the logic into smaller helper functions for clarity and testability.
229    /// Each helper is commented to show how the final gas parameters are derived.
230    ///
231    /// 1. Determine if the transaction is EIP1559 or Legacy.
232    /// 2. Calculate minimum bump requirements (e.g., +10%).
233    /// 3. Compare with current network prices to decide how much to bump.
234    /// 4. Apply any relayer gas price caps.
235    /// 5. Return the final bumped gas parameters.
236    ///
237    /// The returned PriceParams includes an is_min_bumped flag that indicates whether
238    /// the calculated gas parameters meet the minimum bump requirements.
239    pub async fn calculate_bumped_gas_price(
240        &self,
241        tx_data: &EvmTransactionData,
242        relayer: &RelayerRepoModel,
243    ) -> Result<PriceParams, TransactionError> {
244        // Check if network lacks mempool (e.g., Arbitrum) - skip bump and use current prices
245        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            // For mempool-less networks, we don't need to bump - just use current market prices
251            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        // Decide EIP1559 vs Legacy based on presence of maxFeePerGas / maxPriorityFeePerGas vs gasPrice
263        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                // EIP1559
270                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                // Legacy
280                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        // Add extra fee if needed
295        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    /// Computes the bumped gas parameters for an EIP-1559 transaction resubmission.
322    ///
323    /// The function performs the following steps:
324    /// 1. Computes the minimum required fee values by increasing the previous fees by 10%.
325    /// 2. Retrieves the current network market priority fee for the transaction's speed.
326    /// 3. Chooses the new priority fee as either the current market fee (if it meets the 10% increase)
327    ///    or the calculated minimum bump.
328    /// 4. Computes the new maximum fee using two approaches:
329    ///    - Method A: Uses the current base fee, ensuring it meets the minimum bumped max fee.
330    ///    - Method B: Computes a recommended max fee based on a network-specific multiplier plus the new priority fee.
331    ///      The higher value between these two methods is chosen.
332    /// 5. Applies the relayer's gas price cap to both the new priority fee and the new max fee.
333    /// 6. Returns the final capped gas parameters.
334    ///
335    /// Note: All fee values are expected to be in Wei.
336    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        // Calculate the minimum required fees (10% increase over previous values)
347        let min_bump_max_fee = calculate_min_bump(max_fee);
348        let min_bump_max_priority = calculate_min_bump(max_priority_fee);
349
350        // Get the current market priority fee for the given speed.
351        let current_market_priority =
352            Self::get_market_price_for_speed(network_gas_prices, true, speed);
353
354        // Determine the new maxPriorityFeePerGas:
355        // Use the current market fee if it is at least the minimum bumped fee,
356        // otherwise use the minimum bumped priority fee.
357        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        // Compute the new maxFeePerGas using two methods:
364        // Method A: Use the current base fee, but ensure it is not lower than the minimum bumped max fee.
365        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        // Method B: Calculate a recommended max fee based on the base fee multiplied by a network factor,
373        // plus the new priority fee.
374        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        // Choose the higher value from the two methods to be competitive under current network conditions.
381        let final_max_fee = std::cmp::max(bumped_max_fee_per_gas, recommended_max_fee_per_gas);
382
383        // Step 5: Apply the gas price cap to both the new priority fee and the new max fee.
384        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        // Check if the capped values still meet the minimum bump requirements
388        let is_min_bumped =
389            capped_priority >= min_bump_max_priority && capped_max_fee >= min_bump_max_fee;
390
391        // Step 6: Return the final bumped gas parameters.
392        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    /// Handle Legacy bump logic:
403    /// 1) Calculate min bump for gasPrice.
404    /// 2) Compare with current market price for the given speed.
405    /// 3) Apply final caps.
406    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        // Minimum bump
416        let min_bump_gas_price = calculate_min_bump(gas_price);
417
418        // Current market gas price for chosen speed
419        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        // Cap
429        let capped_gas_price = Self::cap_gas_price(bumped_gas_price, gas_price_cap);
430
431        // Check if the capped value still meets the minimum bump requirement
432        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    /// Fetches price params based on the type of transaction (legacy, EIP1559, speed-based).
444    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    /// Handles gas price calculation for legacy transactions.
463    ///
464    /// # Arguments
465    /// * `tx_data` - Transaction data containing the gas price
466    ///
467    /// # Returns
468    /// * `Result<PriceParams, TransactionError>` - Price parameters for legacy transaction
469    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    /// Handles gas price calculation for speed-based transactions.
511    ///
512    /// Determines whether to use legacy or EIP1559 pricing based on network configuration
513    /// and calculates appropriate gas prices based on the requested speed.
514    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    /// Calculates EIP1559 gas prices based on the requested speed.
536    ///
537    /// Uses the gas price service to fetch current network conditions and calculates
538    /// appropriate max fee and priority fee based on the speed setting.
539    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    /// Calculates legacy gas prices based on the requested speed.
565    ///
566    /// Uses the gas price service to fetch current gas prices and applies
567    /// speed-based multipliers for legacy transactions.
568    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    /// Applies gas price caps to the calculated prices.
593    ///
594    /// Ensures that gas prices don't exceed the configured maximum limits and
595    /// maintains proper relationships between different price parameters.
596    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            // Cap the maxFeePerGas
611            let capped_max_fee = Self::cap_gas_price(max_fee, gas_price_cap);
612
613            // Ensure maxPriorityFeePerGas < maxFeePerGas to avoid client errors
614            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            // Handle legacy transaction
618            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    /// Returns the market price for the given speed. If `is_eip1559` is true, use `max_priority_fee_per_gas`,
631    /// otherwise use `legacy_prices`.
632    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    // Calculate number of blocks (as integer)
655    let n_blocks_int = MINUTE_AND_HALF_MS / block_interval_ms;
656
657    // Calculate number of blocks (fractional part in thousandths)
658    let n_blocks_frac = ((MINUTE_AND_HALF_MS % block_interval_ms) * 1000) / block_interval_ms;
659
660    // Calculate multiplier using compound interest formula: (1 + r)^n
661    // For integer part: (1 + 0.125)^n_blocks_int
662    let mut multiplier = PRECISION;
663
664    // Calculate (1.125)^n_blocks_int using repeated multiplication
665    for _ in 0..n_blocks_int {
666        multiplier = (multiplier
667            * (PRECISION + (PRECISION * BASE_FEE_INCREASE_FACTOR_PERCENT) / 1000))
668            / PRECISION;
669    }
670
671    // Handle fractional part with linear approximation
672    // For fractional part: approximately 1 + (fraction * 0.125)
673    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    // Apply maximum cap
680    std::cmp::min(multiplier, MAX_BASE_FEE_MULTIPLIER)
681}
682
683/// Calculate max fee per gas for EIP1559 transactions (all values in wei)
684fn calculate_max_fee_per_gas(
685    base_fee_wei: u128,
686    max_priority_fee_wei: u128,
687    network: &EvmNetwork,
688) -> u128 {
689    // Get multiplier in fixed-point format
690    let multiplier = get_base_fee_multiplier(network);
691
692    // Multiply base fee by multiplier (with proper scaling)
693    let multiplied_base_fee = (base_fee_wei * multiplier) / PRECISION;
694
695    // Add priority fee
696    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, // 2 seconds for optimism to test max cap
714            _ => 12000,         // 12 seconds for mainnet and others
715        };
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, // 1 second for arbitrum
734            _ => 12000,         // 12 seconds for others
735        };
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()], // This makes lacks_mempool() return true
744            chain_id: 42161,
745            required_confirmations: 1,
746            features: vec!["eip1559".to_string()], // This makes it use EIP1559 pricing
747            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        // Create the PriceCalculator with the gas_price_service
789        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        // Create the PriceCalculator
823        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        // Update policies with new EVM policy
914        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), // Higher than cap
923            ..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)); // Should be capped
937    }
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        // 90s with ~12s blocks = ~7.5 blocks => ~2.4 multiplier
944        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        // 2s block time => ~45 blocks => capped at 10.0
949        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; // 100 Gwei
956        let priority_fee = 2_000_000_000u128; // 2 Gwei
957
958        let max_fee = super::calculate_max_fee_per_gas(base_fee, priority_fee, &network);
959        println!("max_fee: {:?}", max_fee);
960        // With mainnet's multiplier (~2.4):
961        // base_fee * multiplier + priority_fee ≈ 100 * 2.4 + 2 ≈ 242 Gwei
962        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        // Mock the gas price service's get_prices_from_json_rpc method
970        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        // Create mock prices
977        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 get_prices_from_json_rpc
994        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        // Mock the network method
1002        let network = create_mock_evm_network("mainnet");
1003        mock_gas_price_service
1004            .expect_network()
1005            .return_const(network);
1006
1007        // Construct our PriceCalculator with the mocked gas service
1008        let pc = PriceCalculator::new(mock_gas_price_service, NetworkExtraFeeCalculator::None);
1009
1010        for (speed, expected_priority_fee) in test_data {
1011            // Call our internal fetch_eip1559_speed_params, which replaced handle_eip1559_speed
1012            let result = pc.fetch_eip1559_speed_params(&speed).await;
1013            assert!(result.is_ok());
1014            let params = result.unwrap();
1015            // Verify max_priority_fee matches expected value
1016            assert_eq!(params.max_priority_fee_per_gas, Some(expected_priority_fee));
1017
1018            // Verify max_fee calculation
1019            // max_fee = base_fee * multiplier + priority_fee
1020            // ≈ (50 * 2.4 + priority_fee_in_gwei) Gwei
1021            let max_fee = params.max_fee_per_gas.unwrap();
1022            let expected_base_portion = 120_000_000_000; // ~50 Gwei * 2.4
1023            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        // Example cap to demonstrate bump capping
1058        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); // >= 10% bump
1075        assert!(bumped.max_priority_fee_per_gas.unwrap() >= 2_200_000_000); // >= 10% bump
1076    }
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, // market priority
1085                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        // Old max_priority_fee: 2.0 Gwei, new market is 1.5 Gwei (less)
1105        // Should use min bump (2.2 Gwei) instead
1106        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); // at least 10% bump
1157    }
1158
1159    #[tokio::test]
1160    async fn test_calculate_bumped_gas_price_missing_params() {
1161        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1162
1163        // Add the missing expectation for get_prices_from_json_rpc
1164        mock_service
1165            .expect_get_prices_from_json_rpc()
1166            .times(1)
1167            .returning(|| Box::pin(async { Ok(GasPrices::default()) }));
1168
1169        // Add the missing expectation for network
1170        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        // Both max_fee_per_gas, max_priority_fee_per_gas, and gas_price absent
1177        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        // Normally, we'd expect ~ (100 Gwei * 2.4) + 8 Gwei > 248 Gwei. We'll cap it at 105 Gwei.
1231        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        // Case 1: Price high enough - should result in is_min_bumped = true
1266        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        // Case 2: Gas price cap too low - should result in is_min_bumped = false
1289        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1290            gas_price_cap: Some(50_000_000_000u128), // Cap is below the min bump for max_fee_per_gas
1291            ..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        // Since min bump is 10%, original was 50 Gwei, min is 55 Gwei, but cap is 50 Gwei
1306        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        // Case 1: Regular case, cap is high enough
1340        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        // Case 2: Cap too low
1362        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1363            gas_price_cap: Some(10_000_000_000u128), // Same as original, preventing the 10% bump
1364            ..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        // Set up mock gas price service
1381        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        // Create a PriceCalculator without extra fee service first
1403        let pc = PriceCalculator::new(mock_gas_service, NetworkExtraFeeCalculator::None);
1404
1405        // Create test transaction and relayer
1406        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        // Call the method under test
1415        let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1416
1417        // Verify extra fee is None when no extra fee service is used
1418        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        // Set up mock gas price service
1426        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        // Create mock extra fee service
1448        let mut mock_extra_fee_service = MockNetworkExtraFeeCalculatorServiceTrait::new();
1449
1450        // Set up expected return value for the extra fee
1451        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        // Use the Mock variant from NetworkExtraFeeCalculator enum
1457        let extra_fee_calculator = NetworkExtraFeeCalculator::Mock(mock_extra_fee_service);
1458
1459        // Create the price calculator with the mock extra fee service
1460        let pc = PriceCalculator::new(mock_gas_service, extra_fee_calculator);
1461
1462        // Create test transaction and relayer
1463        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        // Call the method under test
1472        let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1473
1474        // Verify extra fee was properly included
1475        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); // 1 ETH
1496        let is_eip1559 = true;
1497
1498        let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1499
1500        // Expected: max_fee_per_gas * gas_limit + value
1501        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); // 1 ETH
1518        let is_eip1559 = false;
1519
1520        let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1521
1522        // Expected: gas_price * gas_limit + value
1523        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); // 1 ETH
1540        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; // 20 Gwei
1595        let expected = 22_000_000_000u128; // 22 Gwei (10% bump)
1596        assert_eq!(calculate_min_bump(base_price), expected);
1597
1598        let base_price = 1_000_000_000u128;
1599        let expected = 1_100_000_000u128; // 1.1 Gwei
1600        assert_eq!(calculate_min_bump(base_price), expected);
1601
1602        let base_price = 100_000_000_000u128;
1603        let expected = 110_000_000_000u128; // 110 Gwei
1604        assert_eq!(calculate_min_bump(base_price), expected);
1605    }
1606
1607    #[test]
1608    fn test_calculate_min_bump_edge_cases() {
1609        // Test with zero - should return 1 wei (minimum bump)
1610        assert_eq!(calculate_min_bump(0), 1);
1611
1612        // Test with 1 wei - should return at least 2 wei
1613        let result = calculate_min_bump(1);
1614        assert!(result >= 2);
1615
1616        // Test with very small values where 10% bump rounds down to 0
1617        let base_price = 5u128; // 5 wei
1618        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        // Test with large values to ensure no overflow
1637        let base_price = u128::MAX / 2;
1638        let result = calculate_min_bump(base_price);
1639        assert!(result > base_price);
1640
1641        // Test near maximum value
1642        let base_price = u128::MAX - 1000;
1643        let result = calculate_min_bump(base_price);
1644        // Should not panic and should return a reasonable value
1645        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        // Test that the function always increases by at least 1 wei
1662        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 the network to return a no-mempool network
1686        mock_service
1687            .expect_network()
1688            .return_const(create_mock_no_mempool_network("arbitrum"));
1689
1690        // Mock get_prices_from_json_rpc for get_transaction_price_params call
1691        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        // Also mock get_legacy_prices_from_json_rpc in case it's called
1715        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        // Ensure relayer policy allows EIP1559 pricing
1732        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1733            eip1559_pricing: Some(true),
1734            ..Default::default()
1735        });
1736
1737        // Create a speed-based transaction that would normally be bumped
1738        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), // 1 ETH
1742            ..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        // For no-mempool networks, should use current market prices, not bump
1751        assert_eq!(price_params.is_min_bumped, Some(true));
1752
1753        // The no-mempool network should use the current market prices instead of bumping
1754        // For this test, what matters is that it returns is_min_bumped: true
1755        // The exact pricing method depends on the network configuration
1756
1757        // Verify that some pricing was returned (either EIP1559 or legacy)
1758        assert!(
1759            price_params.max_priority_fee_per_gas.is_some() || price_params.gas_price.is_some()
1760        );
1761
1762        // Should have calculated total cost
1763        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 the network to return a no-mempool network
1771        mock_service
1772            .expect_network()
1773            .return_const(create_mock_no_mempool_network("arbitrum"));
1774
1775        // Mock get_legacy_prices_from_json_rpc for get_transaction_price_params call
1776        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        // Force legacy pricing
1794        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1795            eip1559_pricing: Some(false),
1796            ..Default::default()
1797        });
1798
1799        // Create a speed-based transaction that would normally be bumped
1800        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), // 1 ETH
1804            ..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        // For no-mempool networks, should use current market prices, not bump
1813        assert_eq!(price_params.is_min_bumped, Some(true));
1814
1815        // Should return current market prices - verify that some pricing was returned
1816        assert!(
1817            price_params.max_priority_fee_per_gas.is_some() || price_params.gas_price.is_some()
1818        );
1819
1820        // Should have calculated total cost
1821        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        // Test EIP1559 transaction on regular network (should bump)
1827        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        // Regular network should return some pricing (either EIP1559 or legacy)
1867        assert!(
1868            result_regular.max_priority_fee_per_gas.is_some() || result_regular.gas_price.is_some()
1869        );
1870
1871        // Test same transaction on no-mempool network (should not bump)
1872        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        // No-mempool network should use current market prices
1894        assert_eq!(result_no_mempool.is_min_bumped, Some(true));
1895
1896        // Both networks should return some pricing
1897        assert!(
1898            result_no_mempool.max_priority_fee_per_gas.is_some()
1899                || result_no_mempool.gas_price.is_some()
1900        );
1901
1902        // The key difference is that no-mempool networks should set is_min_bumped to true
1903        // Regular networks may or may not set is_min_bumped depending on the actual implementation
1904        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        // Create a network that returns true for is_arbitrum() but not lacks_mempool()
1912        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, // 1 second for arbitrum
1917            is_testnet: false,
1918            tags: vec!["arbitrum-based".to_string()], // This makes is_arbitrum() return true
1919            chain_id: 42161,
1920            required_confirmations: 1,
1921            features: vec!["eip1559".to_string()],
1922            symbol: "ETH".to_string(),
1923        };
1924
1925        // Mock the network to return our arbitrum network
1926        mock_service.expect_network().return_const(arbitrum_network);
1927
1928        // Mock get_prices_from_json_rpc for get_transaction_price_params call
1929        let mock_prices = GasPrices {
1930            legacy_prices: SpeedPrices {
1931                safe_low: 100_000_000, // 0.1 Gwei (typical for Arbitrum)
1932                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, // 0.01 Gwei
1938                average: 20_000_000,
1939                fast: 30_000_000,
1940                fastest: 40_000_000,
1941            },
1942            base_fee_per_gas: 100_000_000, // 0.1 Gwei
1943        };
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        // Create a speed-based transaction that would normally be bumped on regular networks
1956        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), // 1 ETH
1960            ..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        // For Arbitrum networks (is_arbitrum() == true), should skip bump and use current market prices
1969        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        // Should return pricing based on current market conditions, not bumped prices
1976        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        // Should have calculated total cost
1982        assert!(
1983            price_params.total_cost > U256::ZERO,
1984            "Should have non-zero total cost"
1985        );
1986
1987        // Verify that the prices returned are based on current market (Speed::Fast)
1988        // and not bumped versions of existing transaction prices
1989        if let Some(priority_fee) = price_params.max_priority_fee_per_gas {
1990            // For Speed::Fast, should be around 30_000_000 (0.03 Gwei) based on our mock
1991            assert!(
1992                priority_fee <= 50_000_000, // Should be reasonable for current market, not a bump
1993                "Priority fee should be based on current market, not bumped: {}",
1994                priority_fee
1995            );
1996        }
1997    }
1998}