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        evm_gas_price::{EvmGasPriceServiceTrait, GasPrices},
45        gas::price_params_handler::PriceParamsHandler,
46    },
47};
48
49#[cfg(test)]
50use mockall::automock;
51
52#[async_trait::async_trait]
53#[cfg_attr(test, automock)]
54pub trait PriceCalculatorTrait: Send + Sync {
55    async fn get_transaction_price_params(
56        &self,
57        tx_data: &EvmTransactionData,
58        relayer: &RelayerRepoModel,
59    ) -> Result<PriceParams, TransactionError>;
60
61    async fn calculate_bumped_gas_price(
62        &self,
63        tx_data: &EvmTransactionData,
64        relayer: &RelayerRepoModel,
65    ) -> Result<PriceParams, TransactionError>;
66}
67
68const PRECISION: u128 = 1_000_000_000; // 10^9 (similar to Gwei)
69const MINUTE_AND_HALF_MS: u128 = 90000;
70const BASE_FEE_INCREASE_RATE: f64 = 1.125; // 12.5% increase per block (1 + 0.125)
71const MAX_BASE_FEE_MULTIPLIER: u128 = 10 * PRECISION; // 10.0 * PRECISION
72
73#[derive(Debug, Clone)]
74pub struct PriceParams {
75    pub gas_price: Option<u128>,
76    pub max_fee_per_gas: Option<u128>,
77    pub max_priority_fee_per_gas: Option<u128>,
78    pub is_min_bumped: Option<bool>,
79    pub extra_fee: Option<U256>,
80    pub total_cost: U256,
81}
82
83impl PriceParams {
84    pub fn calculate_total_cost(&self, is_eip1559: bool, gas_limit: u64, value: U256) -> U256 {
85        match is_eip1559 {
86            true => {
87                U256::from(self.max_fee_per_gas.unwrap_or(0)) * U256::from(gas_limit)
88                    + value
89                    + self.extra_fee.unwrap_or(U256::ZERO)
90            }
91            false => {
92                U256::from(self.gas_price.unwrap_or(0)) * U256::from(gas_limit)
93                    + value
94                    + self.extra_fee.unwrap_or(U256::ZERO)
95            }
96        }
97    }
98}
99
100/// Safely calculates the minimum required gas price for a replacement transaction.
101/// Uses saturating arithmetic to prevent overflow and maintains precision.
102///
103/// # Arguments
104///
105/// * `base_price` - The original gas price to calculate bump from
106///
107/// # Returns
108///
109/// The minimum required price for replacement, or `u128::MAX` if overflow would occur.
110pub fn calculate_min_bump(base_price: u128) -> u128 {
111    // Convert MIN_BUMP_FACTOR to a rational representation to avoid floating point precision issues
112    // MIN_BUMP_FACTOR = 1.1 = 11/10
113    const BUMP_NUMERATOR: u128 = 11;
114    const BUMP_DENOMINATOR: u128 = 10;
115
116    let bumped_price = base_price
117        .saturating_mul(BUMP_NUMERATOR)
118        .saturating_div(BUMP_DENOMINATOR);
119
120    // Ensure we always bump by at least 1 wei to guarantee replacement
121    std::cmp::max(bumped_price, base_price.saturating_add(1))
122}
123
124/// Primary struct for calculating gas prices with an injected `EvmGasPriceServiceTrait`.
125pub struct PriceCalculator<G: EvmGasPriceServiceTrait> {
126    gas_price_service: G,
127    price_params_handler: Option<PriceParamsHandler>,
128}
129
130#[async_trait::async_trait]
131impl<G> PriceCalculatorTrait for PriceCalculator<G>
132where
133    G: EvmGasPriceServiceTrait + Send + Sync,
134{
135    async fn get_transaction_price_params(
136        &self,
137        tx_data: &EvmTransactionData,
138        relayer: &RelayerRepoModel,
139    ) -> Result<PriceParams, TransactionError> {
140        PriceCalculator::<G>::get_transaction_price_params(self, tx_data, relayer).await
141    }
142
143    async fn calculate_bumped_gas_price(
144        &self,
145        tx_data: &EvmTransactionData,
146        relayer: &RelayerRepoModel,
147    ) -> Result<PriceParams, TransactionError> {
148        PriceCalculator::<G>::calculate_bumped_gas_price(self, tx_data, relayer).await
149    }
150}
151
152impl<G> PriceCalculator<G>
153where
154    G: EvmGasPriceServiceTrait,
155{
156    pub fn new(gas_price_service: G, price_params_handler: Option<PriceParamsHandler>) -> Self {
157        Self {
158            gas_price_service,
159            price_params_handler,
160        }
161    }
162
163    /// Calculates transaction price parameters based on the transaction type and network conditions.
164    ///
165    /// This function determines the appropriate gas pricing strategy based on the transaction type:
166    /// - For legacy transactions: calculates gas_price
167    /// - For EIP1559 transactions: calculates max_fee_per_gas and max_priority_fee_per_gas
168    /// - For speed-based transactions: automatically chooses between legacy and EIP1559 based on network support
169    ///
170    /// # Arguments
171    /// * `tx_data` - Transaction data containing type and pricing information
172    /// * `relayer` - Relayer configuration including pricing policies and caps
173    /// * `gas_price_service` - Service for fetching current gas prices from the network
174    /// * `provider` - Network provider for accessing blockchain data
175    ///
176    /// # Returns
177    /// * `Result<PriceParams, TransactionError>` - Calculated price parameters or error
178    pub async fn get_transaction_price_params(
179        &self,
180        tx_data: &EvmTransactionData,
181        relayer: &RelayerRepoModel,
182    ) -> Result<PriceParams, TransactionError> {
183        let mut price_final_params = self
184            .fetch_price_params_based_on_tx_type(tx_data, relayer)
185            .await?;
186
187        // Apply gas price caps and constraints
188        self.apply_gas_price_cap_and_constraints(&mut price_final_params, relayer)?;
189
190        // Use price params handler if available for custom network pricing and finalize
191        self.finalize_price_params(relayer, tx_data, price_final_params)
192            .await
193    }
194
195    /// Computes bumped gas price for transaction resubmission, factoring in network conditions.
196    ///
197    /// This refactor breaks the logic into smaller helper functions for clarity and testability.
198    /// Each helper is commented to show how the final gas parameters are derived.
199    ///
200    /// 1. Determine if the transaction is EIP1559 or Legacy.
201    /// 2. Calculate minimum bump requirements (e.g., +10%).
202    /// 3. Compare with current network prices to decide how much to bump.
203    /// 4. Apply any relayer gas price caps.
204    /// 5. Return the final bumped gas parameters.
205    ///
206    /// The returned PriceParams includes an is_min_bumped flag that indicates whether
207    /// the calculated gas parameters meet the minimum bump requirements.
208    pub async fn calculate_bumped_gas_price(
209        &self,
210        tx_data: &EvmTransactionData,
211        relayer: &RelayerRepoModel,
212    ) -> Result<PriceParams, TransactionError> {
213        // Check if network lacks mempool (e.g., Arbitrum) - skip bump and use current prices
214        if self.gas_price_service.network().lacks_mempool()
215            || self.gas_price_service.network().is_arbitrum()
216        {
217            let mut price_params = self.get_transaction_price_params(tx_data, relayer).await?;
218
219            // For mempool-less networks, we don't need to bump - just use current market prices
220            price_params.is_min_bumped = Some(true);
221            return Ok(price_params);
222        }
223
224        let network_gas_prices = self.gas_price_service.get_prices_from_json_rpc().await?;
225        let relayer_gas_price_cap = relayer
226            .policies
227            .get_evm_policy()
228            .gas_price_cap
229            .unwrap_or(u128::MAX);
230
231        // Decide EIP1559 vs Legacy based on presence of maxFeePerGas / maxPriorityFeePerGas vs gasPrice
232        let bumped_price_params = match (
233            tx_data.max_fee_per_gas,
234            tx_data.max_priority_fee_per_gas,
235            tx_data.gas_price,
236        ) {
237            (Some(max_fee), Some(max_priority_fee), _) => {
238                // EIP1559
239                self.handle_eip1559_bump(
240                    &network_gas_prices,
241                    relayer_gas_price_cap,
242                    tx_data.speed.as_ref(),
243                    max_fee,
244                    max_priority_fee,
245                )?
246            }
247            (None, None, Some(gas_price)) => {
248                // Legacy
249                self.handle_legacy_bump(
250                    &network_gas_prices,
251                    relayer_gas_price_cap,
252                    tx_data.speed.as_ref(),
253                    gas_price,
254                )?
255            }
256            _ => {
257                return Err(TransactionError::InvalidType(
258                    "Transaction missing required gas price parameters".to_string(),
259                ))
260            }
261        };
262
263        // Use price params handler if available for custom network pricing and finalize
264        self.finalize_price_params(relayer, tx_data, bumped_price_params)
265            .await
266    }
267
268    /// Computes the bumped gas parameters for an EIP-1559 transaction resubmission.
269    ///
270    /// The function performs the following steps:
271    /// 1. Computes the minimum required fee values by increasing the previous fees by 10%.
272    /// 2. Retrieves the current network market priority fee for the transaction's speed.
273    /// 3. Chooses the new priority fee as either the current market fee (if it meets the 10% increase)
274    ///    or the calculated minimum bump.
275    /// 4. Computes the new maximum fee using two approaches:
276    ///    - Method A: Uses the current base fee, ensuring it meets the minimum bumped max fee.
277    ///    - Method B: Computes a recommended max fee based on a network-specific multiplier plus the new priority fee.
278    ///      The higher value between these two methods is chosen.
279    /// 5. Applies the relayer's gas price cap to both the new priority fee and the new max fee.
280    /// 6. Returns the final capped gas parameters.
281    ///
282    /// Note: All fee values are expected to be in Wei.
283    fn handle_eip1559_bump(
284        &self,
285        network_gas_prices: &GasPrices,
286        gas_price_cap: u128,
287        maybe_speed: Option<&Speed>,
288        max_fee: u128,
289        max_priority_fee: u128,
290    ) -> Result<PriceParams, TransactionError> {
291        let speed = maybe_speed.unwrap_or(&DEFAULT_TRANSACTION_SPEED);
292
293        // Calculate the minimum required fees (10% increase over previous values)
294        let min_bump_max_fee = calculate_min_bump(max_fee);
295        let min_bump_max_priority = calculate_min_bump(max_priority_fee);
296
297        // Get the current market priority fee for the given speed.
298        let current_market_priority =
299            Self::get_market_price_for_speed(network_gas_prices, true, speed);
300
301        // Determine the new maxPriorityFeePerGas:
302        // Use the current market fee if it is at least the minimum bumped fee,
303        // otherwise use the minimum bumped priority fee.
304        let bumped_priority_fee = if current_market_priority >= min_bump_max_priority {
305            current_market_priority
306        } else {
307            min_bump_max_priority
308        };
309
310        // Compute the new maxFeePerGas using two methods:
311        // Method A: Use the current base fee, but ensure it is not lower than the minimum bumped max fee.
312        let base_fee_wei = network_gas_prices.base_fee_per_gas;
313        let bumped_max_fee_per_gas = if base_fee_wei >= min_bump_max_fee {
314            base_fee_wei
315        } else {
316            min_bump_max_fee
317        };
318
319        // Method B: Calculate a recommended max fee based on the base fee multiplied by a network factor,
320        // plus the new priority fee.
321        let recommended_max_fee_per_gas = calculate_max_fee_per_gas(
322            base_fee_wei,
323            bumped_priority_fee,
324            self.gas_price_service.network(),
325        );
326
327        // Choose the higher value from the two methods to be competitive under current network conditions.
328        let final_max_fee = std::cmp::max(bumped_max_fee_per_gas, recommended_max_fee_per_gas);
329
330        // Step 5: Apply the gas price cap to both the new priority fee and the new max fee.
331        let capped_priority = Self::cap_gas_price(bumped_priority_fee, gas_price_cap);
332        let capped_max_fee = Self::cap_gas_price(final_max_fee, gas_price_cap);
333
334        // Check if the capped values still meet the minimum bump requirements
335        let is_min_bumped =
336            capped_priority >= min_bump_max_priority && capped_max_fee >= min_bump_max_fee;
337
338        // Step 6: Return the final bumped gas parameters.
339        Ok(PriceParams {
340            gas_price: None,
341            max_priority_fee_per_gas: Some(capped_priority),
342            max_fee_per_gas: Some(capped_max_fee),
343            is_min_bumped: Some(is_min_bumped),
344            extra_fee: None,
345            total_cost: U256::ZERO,
346        })
347    }
348
349    /// Handle Legacy bump logic:
350    /// 1) Calculate min bump for gasPrice.
351    /// 2) Compare with current market price for the given speed.
352    /// 3) Apply final caps.
353    fn handle_legacy_bump(
354        &self,
355        network_gas_prices: &GasPrices,
356        gas_price_cap: u128,
357        maybe_speed: Option<&Speed>,
358        gas_price: u128,
359    ) -> Result<PriceParams, TransactionError> {
360        let speed = maybe_speed.unwrap_or(&Speed::Fast);
361
362        // Minimum bump
363        let min_bump_gas_price = calculate_min_bump(gas_price);
364
365        // Current market gas price for chosen speed
366        let current_market_price =
367            Self::get_market_price_for_speed(network_gas_prices, false, speed);
368
369        let bumped_gas_price = if current_market_price >= min_bump_gas_price {
370            current_market_price
371        } else {
372            min_bump_gas_price
373        };
374
375        // Cap
376        let capped_gas_price = Self::cap_gas_price(bumped_gas_price, gas_price_cap);
377
378        // Check if the capped value still meets the minimum bump requirement
379        let is_min_bumped = capped_gas_price >= min_bump_gas_price;
380
381        Ok(PriceParams {
382            gas_price: Some(capped_gas_price),
383            max_priority_fee_per_gas: None,
384            max_fee_per_gas: None,
385            is_min_bumped: Some(is_min_bumped),
386            extra_fee: None,
387            total_cost: U256::ZERO,
388        })
389    }
390    /// Fetches price params based on the type of transaction (legacy, EIP1559, speed-based).
391    async fn fetch_price_params_based_on_tx_type(
392        &self,
393        tx_data: &EvmTransactionData,
394        relayer: &RelayerRepoModel,
395    ) -> Result<PriceParams, TransactionError> {
396        if tx_data.is_legacy() {
397            self.fetch_legacy_price_params(tx_data)
398        } else if tx_data.is_eip1559() {
399            self.fetch_eip1559_price_params(tx_data)
400        } else if tx_data.is_speed() {
401            self.fetch_speed_price_params(tx_data, relayer).await
402        } else {
403            Err(TransactionError::NotSupported(
404                "Invalid transaction type".to_string(),
405            ))
406        }
407    }
408
409    /// Handles gas price calculation for legacy transactions.
410    ///
411    /// # Arguments
412    /// * `tx_data` - Transaction data containing the gas price
413    ///
414    /// # Returns
415    /// * `Result<PriceParams, TransactionError>` - Price parameters for legacy transaction
416    fn fetch_legacy_price_params(
417        &self,
418        tx_data: &EvmTransactionData,
419    ) -> Result<PriceParams, TransactionError> {
420        let gas_price = tx_data.gas_price.ok_or(TransactionError::NotSupported(
421            "Gas price is required for legacy transactions".to_string(),
422        ))?;
423        Ok(PriceParams {
424            gas_price: Some(gas_price),
425            max_fee_per_gas: None,
426            max_priority_fee_per_gas: None,
427            is_min_bumped: None,
428            extra_fee: None,
429            total_cost: U256::ZERO,
430        })
431    }
432
433    fn fetch_eip1559_price_params(
434        &self,
435        tx_data: &EvmTransactionData,
436    ) -> Result<PriceParams, TransactionError> {
437        let max_fee = tx_data
438            .max_fee_per_gas
439            .ok_or(TransactionError::NotSupported(
440                "Max fee per gas is required for EIP1559 transactions".to_string(),
441            ))?;
442        let max_priority_fee =
443            tx_data
444                .max_priority_fee_per_gas
445                .ok_or(TransactionError::NotSupported(
446                    "Max priority fee per gas is required for EIP1559 transactions".to_string(),
447                ))?;
448        Ok(PriceParams {
449            gas_price: None,
450            max_fee_per_gas: Some(max_fee),
451            max_priority_fee_per_gas: Some(max_priority_fee),
452            is_min_bumped: None,
453            extra_fee: None,
454            total_cost: U256::ZERO,
455        })
456    }
457    /// Handles gas price calculation for speed-based transactions.
458    ///
459    /// Determines whether to use legacy or EIP1559 pricing based on network configuration
460    /// and calculates appropriate gas prices based on the requested speed.
461    async fn fetch_speed_price_params(
462        &self,
463        tx_data: &EvmTransactionData,
464        relayer: &RelayerRepoModel,
465    ) -> Result<PriceParams, TransactionError> {
466        let speed = tx_data
467            .speed
468            .as_ref()
469            .ok_or(TransactionError::NotSupported(
470                "Speed is required".to_string(),
471            ))?;
472        let use_legacy = relayer.policies.get_evm_policy().eip1559_pricing == Some(false)
473            || self.gas_price_service.network().is_legacy();
474
475        if use_legacy {
476            self.fetch_legacy_speed_params(speed).await
477        } else {
478            self.fetch_eip1559_speed_params(speed).await
479        }
480    }
481
482    /// Calculates EIP1559 gas prices based on the requested speed.
483    ///
484    /// Uses the gas price service to fetch current network conditions and calculates
485    /// appropriate max fee and priority fee based on the speed setting.
486    async fn fetch_eip1559_speed_params(
487        &self,
488        speed: &Speed,
489    ) -> Result<PriceParams, TransactionError> {
490        let prices = self.gas_price_service.get_prices_from_json_rpc().await?;
491        let priority_fee = match speed {
492            Speed::SafeLow => prices.max_priority_fee_per_gas.safe_low,
493            Speed::Average => prices.max_priority_fee_per_gas.average,
494            Speed::Fast => prices.max_priority_fee_per_gas.fast,
495            Speed::Fastest => prices.max_priority_fee_per_gas.fastest,
496        };
497        let max_fee = calculate_max_fee_per_gas(
498            prices.base_fee_per_gas,
499            priority_fee,
500            self.gas_price_service.network(),
501        );
502        Ok(PriceParams {
503            gas_price: None,
504            max_fee_per_gas: Some(max_fee),
505            max_priority_fee_per_gas: Some(priority_fee),
506            is_min_bumped: None,
507            extra_fee: None,
508            total_cost: U256::ZERO,
509        })
510    }
511    /// Calculates legacy gas prices based on the requested speed.
512    ///
513    /// Uses the gas price service to fetch current gas prices and applies
514    /// speed-based multipliers for legacy transactions.
515    async fn fetch_legacy_speed_params(
516        &self,
517        speed: &Speed,
518    ) -> Result<PriceParams, TransactionError> {
519        let prices = self
520            .gas_price_service
521            .get_legacy_prices_from_json_rpc()
522            .await?;
523        let gas_price = match speed {
524            Speed::SafeLow => prices.safe_low,
525            Speed::Average => prices.average,
526            Speed::Fast => prices.fast,
527            Speed::Fastest => prices.fastest,
528        };
529        Ok(PriceParams {
530            gas_price: Some(gas_price),
531            max_fee_per_gas: None,
532            max_priority_fee_per_gas: None,
533            is_min_bumped: None,
534            extra_fee: None,
535            total_cost: U256::ZERO,
536        })
537    }
538
539    /// Applies gas price caps and constraints to PriceParams in place.
540    ///
541    /// Ensures that gas prices don't exceed the configured maximum limits and
542    /// maintains proper relationships between different price parameters.
543    /// This method modifies the provided PriceParams struct directly.
544    fn apply_gas_price_cap_and_constraints(
545        &self,
546        price_params: &mut PriceParams,
547        relayer: &RelayerRepoModel,
548    ) -> Result<(), TransactionError> {
549        let gas_price_cap = relayer
550            .policies
551            .get_evm_policy()
552            .gas_price_cap
553            .unwrap_or(u128::MAX);
554
555        if let (Some(max_fee), Some(max_priority)) = (
556            price_params.max_fee_per_gas,
557            price_params.max_priority_fee_per_gas,
558        ) {
559            // Cap the maxFeePerGas
560            let capped_max_fee = Self::cap_gas_price(max_fee, gas_price_cap);
561            price_params.max_fee_per_gas = Some(capped_max_fee);
562
563            // Ensure maxPriorityFeePerGas < maxFeePerGas to avoid client errors
564            price_params.max_priority_fee_per_gas =
565                Some(Self::cap_gas_price(max_priority, capped_max_fee));
566
567            // For EIP1559 transactions, gas_price should be None
568            price_params.gas_price = None;
569        } else {
570            // Handle legacy transaction
571            price_params.gas_price = Some(Self::cap_gas_price(
572                price_params.gas_price.unwrap_or_default(),
573                gas_price_cap,
574            ));
575
576            // For legacy transactions, EIP1559 fields should be None
577            price_params.max_fee_per_gas = None;
578            price_params.max_priority_fee_per_gas = None;
579        }
580
581        Ok(())
582    }
583
584    fn cap_gas_price(price: u128, cap: u128) -> u128 {
585        std::cmp::min(price, cap)
586    }
587
588    /// Applies price params handler and finalizes price parameters.
589    async fn finalize_price_params(
590        &self,
591        relayer: &RelayerRepoModel,
592        tx_data: &EvmTransactionData,
593        mut price_params: PriceParams,
594    ) -> Result<PriceParams, TransactionError> {
595        let is_eip1559 = tx_data.is_eip1559();
596
597        // Apply price params handler if available
598        if let Some(handler) = &self.price_params_handler {
599            price_params = handler.handle_price_params(tx_data, price_params).await?;
600
601            // Re-apply cap after handler in case it changed fee fields
602            self.apply_gas_price_cap_and_constraints(&mut price_params, relayer)?;
603        }
604
605        if price_params.total_cost == U256::ZERO {
606            price_params.total_cost = price_params.calculate_total_cost(
607                is_eip1559,
608                tx_data.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
609                U256::from(tx_data.value),
610            );
611        }
612
613        Ok(price_params)
614    }
615
616    /// Returns the market price for the given speed. If `is_eip1559` is true, use `max_priority_fee_per_gas`,
617    /// otherwise use `legacy_prices`.
618    fn get_market_price_for_speed(prices: &GasPrices, is_eip1559: bool, speed: &Speed) -> u128 {
619        if is_eip1559 {
620            match speed {
621                Speed::SafeLow => prices.max_priority_fee_per_gas.safe_low,
622                Speed::Average => prices.max_priority_fee_per_gas.average,
623                Speed::Fast => prices.max_priority_fee_per_gas.fast,
624                Speed::Fastest => prices.max_priority_fee_per_gas.fastest,
625            }
626        } else {
627            match speed {
628                Speed::SafeLow => prices.legacy_prices.safe_low,
629                Speed::Average => prices.legacy_prices.average,
630                Speed::Fast => prices.legacy_prices.fast,
631                Speed::Fastest => prices.legacy_prices.fastest,
632            }
633        }
634    }
635}
636
637fn get_base_fee_multiplier(network: &EvmNetwork) -> u128 {
638    let block_interval_ms = network.average_blocktime().map(|d| d.as_millis()).unwrap();
639
640    // Calculate number of blocks in 90 seconds
641    let n_blocks = MINUTE_AND_HALF_MS / block_interval_ms;
642
643    // Calculate multiplier: BASE_FEE_INCREASE_RATE^n_blocks
644    let multiplier_f64 = BASE_FEE_INCREASE_RATE.powi(n_blocks as i32);
645
646    // Convert back to fixed-point u128
647    let multiplier = (multiplier_f64 * PRECISION as f64) as u128;
648
649    // Apply maximum cap
650    std::cmp::min(multiplier, MAX_BASE_FEE_MULTIPLIER)
651}
652
653/// Calculate max fee per gas for EIP1559 transactions (all values in wei)
654fn calculate_max_fee_per_gas(
655    base_fee_wei: u128,
656    max_priority_fee_wei: u128,
657    network: &EvmNetwork,
658) -> u128 {
659    // Get multiplier in fixed-point format
660    let multiplier = get_base_fee_multiplier(network);
661
662    // Multiply base fee by multiplier (with proper scaling)
663    let multiplied_base_fee = (base_fee_wei * multiplier) / PRECISION;
664
665    // Add priority fee
666    multiplied_base_fee + max_priority_fee_wei
667}
668#[cfg(test)]
669mod tests {
670    use super::*;
671    use crate::constants::{ARBITRUM_BASED_TAG, NO_MEMPOOL_TAG};
672    use crate::models::{
673        evm::Speed, EvmNetwork, EvmTransactionData, NetworkType, RelayerEvmPolicy,
674        RelayerNetworkPolicy, RelayerRepoModel, U256,
675    };
676    use crate::services::{
677        evm_gas_price::{EvmGasPriceService, GasPrices, MockEvmGasPriceServiceTrait, SpeedPrices},
678        gas::handlers::test_mock::MockPriceHandler,
679        provider::MockEvmProviderTrait,
680    };
681    use futures::FutureExt;
682
683    fn create_mock_evm_network(name: &str) -> EvmNetwork {
684        let average_blocktime_ms = match name {
685            "optimism" => 2000, // 2 seconds for optimism to test max cap
686            _ => 12000,         // 12 seconds for mainnet and others
687        };
688
689        EvmNetwork {
690            network: name.to_string(),
691            rpc_urls: vec!["https://rpc.example.com".to_string()],
692            explorer_urls: None,
693            average_blocktime_ms,
694            is_testnet: true,
695            tags: vec![],
696            chain_id: 1337,
697            required_confirmations: 1,
698            features: vec![],
699            symbol: "ETH".to_string(),
700            gas_price_cache: None,
701        }
702    }
703
704    fn create_mock_no_mempool_network(name: &str) -> EvmNetwork {
705        let average_blocktime_ms = match name {
706            "arbitrum" => 1000, // 1 second for arbitrum
707            _ => 12000,         // 12 seconds for others
708        };
709
710        EvmNetwork {
711            network: name.to_string(),
712            rpc_urls: vec!["https://rpc.example.com".to_string()],
713            explorer_urls: None,
714            average_blocktime_ms,
715            is_testnet: true,
716            tags: vec![NO_MEMPOOL_TAG.to_string()], // This makes lacks_mempool() return true
717            chain_id: 42161,
718            required_confirmations: 1,
719            features: vec!["eip1559".to_string()], // This makes it use EIP1559 pricing
720            symbol: "ETH".to_string(),
721            gas_price_cache: None,
722        }
723    }
724
725    fn create_mock_relayer() -> RelayerRepoModel {
726        RelayerRepoModel {
727            id: "test-relayer".to_string(),
728            name: "Test Relayer".to_string(),
729            network: "mainnet".to_string(),
730            network_type: NetworkType::Evm,
731            address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string(),
732            policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy::default()),
733            paused: false,
734            notification_id: None,
735            signer_id: "test-signer".to_string(),
736            system_disabled: false,
737            custom_rpc_urls: None,
738            ..Default::default()
739        }
740    }
741
742    #[tokio::test]
743    async fn test_legacy_transaction() {
744        let mut provider = MockEvmProviderTrait::new();
745        provider
746            .expect_get_balance()
747            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
748
749        let relayer = create_mock_relayer();
750        let gas_price_service =
751            EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"), None);
752
753        let tx_data = EvmTransactionData {
754            gas_price: Some(20000000000),
755            ..Default::default()
756        };
757
758        let mut provider = MockEvmProviderTrait::new();
759        provider
760            .expect_get_balance()
761            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
762
763        // Create the PriceCalculator with the gas_price_service
764        let pc = PriceCalculator::new(gas_price_service, None);
765
766        let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
767        assert!(result.is_ok());
768        let params = result.unwrap();
769        assert_eq!(params.gas_price, Some(20000000000));
770        assert!(params.max_fee_per_gas.is_none());
771        assert!(params.max_priority_fee_per_gas.is_none());
772    }
773
774    #[tokio::test]
775    async fn test_eip1559_transaction() {
776        let mut provider = MockEvmProviderTrait::new();
777        provider
778            .expect_get_balance()
779            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
780
781        let relayer = create_mock_relayer();
782        let gas_price_service =
783            EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"), None);
784
785        let tx_data = EvmTransactionData {
786            gas_price: None,
787            max_fee_per_gas: Some(30000000000),
788            max_priority_fee_per_gas: Some(2000000000),
789            ..Default::default()
790        };
791
792        let mut provider = MockEvmProviderTrait::new();
793        provider
794            .expect_get_balance()
795            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
796
797        // Create the PriceCalculator
798        let pc = PriceCalculator::new(gas_price_service, None);
799
800        let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
801        assert!(result.is_ok());
802        let params = result.unwrap();
803        assert!(params.gas_price.is_none());
804        assert_eq!(params.max_fee_per_gas, Some(30000000000));
805        assert_eq!(params.max_priority_fee_per_gas, Some(2000000000));
806    }
807
808    #[tokio::test]
809    async fn test_speed_legacy_based_transaction() {
810        let mut provider = MockEvmProviderTrait::new();
811        provider
812            .expect_get_balance()
813            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
814        provider
815            .expect_get_gas_price()
816            .returning(|| async { Ok(20000000000) }.boxed());
817
818        let relayer = create_mock_relayer();
819        let gas_price_service =
820            EvmGasPriceService::new(provider, create_mock_evm_network("celo"), None);
821
822        let tx_data = EvmTransactionData {
823            gas_price: None,
824            speed: Some(Speed::Fast),
825            ..Default::default()
826        };
827
828        let mut provider = MockEvmProviderTrait::new();
829        provider
830            .expect_get_balance()
831            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
832        provider
833            .expect_get_gas_price()
834            .returning(|| async { Ok(20000000000) }.boxed());
835
836        let pc = PriceCalculator::new(gas_price_service, None);
837
838        let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
839        assert!(result.is_ok());
840        let params = result.unwrap();
841        assert!(
842            params.gas_price.is_some()
843                || (params.max_fee_per_gas.is_some() && params.max_priority_fee_per_gas.is_some())
844        );
845    }
846
847    #[tokio::test]
848    async fn test_invalid_transaction_type() {
849        let mut provider = MockEvmProviderTrait::new();
850        provider
851            .expect_get_balance()
852            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
853
854        let relayer = create_mock_relayer();
855        let gas_price_service =
856            EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"), None);
857
858        let tx_data = EvmTransactionData {
859            gas_price: None,
860            ..Default::default()
861        };
862
863        let mut provider = MockEvmProviderTrait::new();
864        provider
865            .expect_get_balance()
866            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
867
868        let pc = PriceCalculator::new(gas_price_service, None);
869
870        let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
871        assert!(result.is_err());
872        assert!(matches!(
873            result.unwrap_err(),
874            TransactionError::NotSupported(_)
875        ));
876    }
877
878    #[tokio::test]
879    async fn test_gas_price_cap() {
880        let mut provider = MockEvmProviderTrait::new();
881        provider
882            .expect_get_balance()
883            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
884
885        let mut relayer = create_mock_relayer();
886        let gas_price_service =
887            EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"), None);
888
889        // Update policies with new EVM policy
890        let evm_policy = RelayerEvmPolicy {
891            gas_price_cap: Some(10000000000),
892            eip1559_pricing: Some(true),
893            ..RelayerEvmPolicy::default()
894        };
895        relayer.policies = RelayerNetworkPolicy::Evm(evm_policy);
896
897        let tx_data = EvmTransactionData {
898            gas_price: Some(20000000000), // Higher than cap
899            ..Default::default()
900        };
901
902        let mut provider = MockEvmProviderTrait::new();
903        provider
904            .expect_get_balance()
905            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
906
907        let pc = PriceCalculator::new(gas_price_service, None);
908
909        let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
910        assert!(result.is_ok());
911        let params = result.unwrap();
912        assert_eq!(params.gas_price, Some(10000000000)); // Should be capped
913    }
914
915    #[test]
916    fn test_get_base_fee_multiplier() {
917        let mainnet = create_mock_evm_network("mainnet");
918        let multiplier = super::get_base_fee_multiplier(&mainnet);
919        // 90s with ~12s blocks = ~7.5 blocks => ~2.28x multiplier (binary exponentiation result)
920        assert!(multiplier > 2_200_000_000 && multiplier < 2_400_000_000);
921
922        let optimism = create_mock_evm_network("optimism");
923        let multiplier = super::get_base_fee_multiplier(&optimism);
924        // 2s block time => ~45 blocks => capped at 10.0
925        assert_eq!(multiplier, MAX_BASE_FEE_MULTIPLIER);
926    }
927
928    #[test]
929    fn test_get_base_fee_multiplier_overflow_protection() {
930        // Test multiplier cap for fast blockchains
931        let mut test_network = create_mock_evm_network("test");
932
933        // Test with 1ms block time (90000 blocks in 90s) - astronomical multiplier
934        test_network.average_blocktime_ms = 1;
935        let multiplier = super::get_base_fee_multiplier(&test_network);
936        // (1.125)^90000 would be astronomical, capped at 10x
937        assert_eq!(multiplier, MAX_BASE_FEE_MULTIPLIER);
938
939        // Test with 100ms block time (900 blocks in 90s) - very large multiplier
940        test_network.average_blocktime_ms = 100;
941        let multiplier = super::get_base_fee_multiplier(&test_network);
942        // (1.125)^900 would be huge, capped at 10x
943        assert_eq!(multiplier, MAX_BASE_FEE_MULTIPLIER);
944
945        // Test with 1s block time (90 blocks in 90s) - large multiplier
946        test_network.average_blocktime_ms = 1000;
947        let multiplier = super::get_base_fee_multiplier(&test_network);
948        // (1.125)^90 would be very large, capped at 10x
949        assert_eq!(multiplier, MAX_BASE_FEE_MULTIPLIER);
950
951        // Test with 5s block time (18 blocks in 90s, not capped)
952        test_network.average_blocktime_ms = 5000;
953        let multiplier = super::get_base_fee_multiplier(&test_network);
954        // 18 blocks (under cap): (1.125)^18 ≈ 8.33x, should not be capped
955        assert!(multiplier > 8_000_000_000 && multiplier < 9_000_000_000);
956        assert!(multiplier < MAX_BASE_FEE_MULTIPLIER);
957
958        // Test with 10s block time (9 blocks in 90s, not capped)
959        test_network.average_blocktime_ms = 10000;
960        let multiplier = super::get_base_fee_multiplier(&test_network);
961        // 9 blocks (under cap): (1.125)^9 ≈ 2.89x (actual calculation)
962        assert!(multiplier > 2_500_000_000 && multiplier < 3_000_000_000);
963    }
964
965    #[test]
966    fn test_calculate_max_fee_per_gas() {
967        let network = create_mock_evm_network("mainnet");
968        let base_fee = 100_000_000_000u128; // 100 Gwei
969        let priority_fee = 2_000_000_000u128; // 2 Gwei
970
971        let max_fee = super::calculate_max_fee_per_gas(base_fee, priority_fee, &network);
972        // With mainnet's multiplier (~2.28):
973        // base_fee * multiplier + priority_fee ≈ 100 * 2.28 + 2 ≈ 230 Gwei
974        assert!(max_fee > 225_000_000_000 && max_fee < 235_000_000_000);
975    }
976
977    #[tokio::test]
978    async fn test_handle_eip1559_speed() {
979        let mut mock_gas_price_service = MockEvmGasPriceServiceTrait::new();
980
981        // Mock the gas price service's get_prices_from_json_rpc method
982        let test_data = [
983            (Speed::SafeLow, 1_000_000_000),
984            (Speed::Average, 2_000_000_000),
985            (Speed::Fast, 3_000_000_000),
986            (Speed::Fastest, 4_000_000_000),
987        ];
988        // Create mock prices
989        let mock_prices = GasPrices {
990            legacy_prices: SpeedPrices {
991                safe_low: 10_000_000_000,
992                average: 12_500_000_000,
993                fast: 15_000_000_000,
994                fastest: 20_000_000_000,
995            },
996            max_priority_fee_per_gas: SpeedPrices {
997                safe_low: 1_000_000_000,
998                average: 2_000_000_000,
999                fast: 3_000_000_000,
1000                fastest: 4_000_000_000,
1001            },
1002            base_fee_per_gas: 50_000_000_000,
1003        };
1004
1005        // Mock get_prices_from_json_rpc
1006        mock_gas_price_service
1007            .expect_get_prices_from_json_rpc()
1008            .returning(move || {
1009                let prices = mock_prices.clone();
1010                Box::pin(async move { Ok(prices) })
1011            });
1012
1013        // Mock the network method
1014        let network = create_mock_evm_network("mainnet");
1015        mock_gas_price_service
1016            .expect_network()
1017            .return_const(network);
1018
1019        // Construct our PriceCalculator with the mocked gas service
1020        let pc = PriceCalculator::new(mock_gas_price_service, None);
1021
1022        for (speed, expected_priority_fee) in test_data {
1023            // Call our internal fetch_eip1559_speed_params, which replaced handle_eip1559_speed
1024            let result = pc.fetch_eip1559_speed_params(&speed).await;
1025            assert!(result.is_ok());
1026            let params = result.unwrap();
1027            // Verify max_priority_fee matches expected value
1028            assert_eq!(params.max_priority_fee_per_gas, Some(expected_priority_fee));
1029
1030            // Verify max_fee calculation
1031            // max_fee = base_fee * multiplier + priority_fee
1032            // ≈ (50 * 2.4 + priority_fee_in_gwei) Gwei
1033            let max_fee = params.max_fee_per_gas.unwrap();
1034            let expected_base_portion = 120_000_000_000; // ~50 Gwei * 2.4
1035            assert!(max_fee < expected_base_portion + expected_priority_fee + 2_000_000_000);
1036        }
1037    }
1038
1039    #[tokio::test]
1040    async fn test_calculate_bumped_gas_price_eip1559_basic() {
1041        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1042        let mock_prices = GasPrices {
1043            legacy_prices: SpeedPrices {
1044                safe_low: 8_000_000_000,
1045                average: 10_000_000_000,
1046                fast: 12_000_000_000,
1047                fastest: 15_000_000_000,
1048            },
1049            max_priority_fee_per_gas: SpeedPrices {
1050                safe_low: 1_000_000_000,
1051                average: 2_000_000_000,
1052                fast: 3_000_000_000,
1053                fastest: 4_000_000_000,
1054            },
1055            base_fee_per_gas: 50_000_000_000,
1056        };
1057        mock_service
1058            .expect_get_prices_from_json_rpc()
1059            .returning(move || {
1060                let prices = mock_prices.clone();
1061                Box::pin(async move { Ok(prices) })
1062            });
1063        mock_service
1064            .expect_network()
1065            .return_const(create_mock_evm_network("mainnet"));
1066
1067        let pc = PriceCalculator::new(mock_service, None);
1068        let mut relayer = create_mock_relayer();
1069        // Example cap to demonstrate bump capping
1070        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1071            gas_price_cap: Some(300_000_000_000u128),
1072            ..Default::default()
1073        });
1074
1075        let tx_data = EvmTransactionData {
1076            max_fee_per_gas: Some(100_000_000_000),
1077            max_priority_fee_per_gas: Some(2_000_000_000),
1078            speed: Some(Speed::Fast),
1079            ..Default::default()
1080        };
1081
1082        let bumped = pc
1083            .calculate_bumped_gas_price(&tx_data, &relayer)
1084            .await
1085            .unwrap();
1086        assert!(bumped.max_fee_per_gas.unwrap() >= 110_000_000_000); // >= 10% bump
1087        assert!(bumped.max_priority_fee_per_gas.unwrap() >= 2_200_000_000); // >= 10% bump
1088    }
1089
1090    #[tokio::test]
1091    async fn test_calculate_bumped_gas_price_eip1559_market_lower_than_min_bump() {
1092        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1093        let mock_prices = GasPrices {
1094            legacy_prices: SpeedPrices::default(),
1095            max_priority_fee_per_gas: SpeedPrices {
1096                safe_low: 1_500_000_000, // market priority
1097                average: 2_500_000_000,
1098                fast: 2_700_000_000,
1099                fastest: 3_000_000_000,
1100            },
1101            base_fee_per_gas: 30_000_000_000,
1102        };
1103        mock_service
1104            .expect_get_prices_from_json_rpc()
1105            .returning(move || {
1106                let prices = mock_prices.clone();
1107                Box::pin(async move { Ok(prices) })
1108            });
1109        mock_service
1110            .expect_network()
1111            .return_const(create_mock_evm_network("mainnet"));
1112
1113        let pc = PriceCalculator::new(mock_service, None);
1114        let relayer = create_mock_relayer();
1115
1116        // Old max_priority_fee: 2.0 Gwei, new market is 1.5 Gwei (less)
1117        // Should use min bump (2.2 Gwei) instead
1118        let tx_data = EvmTransactionData {
1119            max_fee_per_gas: Some(20_000_000_000),
1120            max_priority_fee_per_gas: Some(2_000_000_000),
1121            speed: Some(Speed::SafeLow),
1122            ..Default::default()
1123        };
1124
1125        let bumped = pc
1126            .calculate_bumped_gas_price(&tx_data, &relayer)
1127            .await
1128            .unwrap();
1129        assert!(bumped.max_priority_fee_per_gas.unwrap() >= 2_200_000_000);
1130        assert!(bumped.max_fee_per_gas.unwrap() > 20_000_000_000);
1131    }
1132
1133    #[tokio::test]
1134    async fn test_calculate_bumped_gas_price_legacy_basic() {
1135        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1136        let mock_prices = GasPrices {
1137            legacy_prices: SpeedPrices {
1138                safe_low: 10_000_000_000,
1139                average: 12_000_000_000,
1140                fast: 14_000_000_000,
1141                fastest: 18_000_000_000,
1142            },
1143            max_priority_fee_per_gas: SpeedPrices::default(),
1144            base_fee_per_gas: 0,
1145        };
1146        mock_service
1147            .expect_get_prices_from_json_rpc()
1148            .returning(move || {
1149                let prices = mock_prices.clone();
1150                Box::pin(async move { Ok(prices) })
1151            });
1152        mock_service
1153            .expect_network()
1154            .return_const(create_mock_evm_network("mainnet"));
1155
1156        let pc = PriceCalculator::new(mock_service, None);
1157        let relayer = create_mock_relayer();
1158        let tx_data = EvmTransactionData {
1159            gas_price: Some(10_000_000_000),
1160            speed: Some(Speed::Fast),
1161            ..Default::default()
1162        };
1163
1164        let bumped = pc
1165            .calculate_bumped_gas_price(&tx_data, &relayer)
1166            .await
1167            .unwrap();
1168        assert!(bumped.gas_price.unwrap() >= 11_000_000_000); // at least 10% bump
1169    }
1170
1171    #[tokio::test]
1172    async fn test_calculate_bumped_gas_price_missing_params() {
1173        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1174
1175        // Add the missing expectation for get_prices_from_json_rpc
1176        mock_service
1177            .expect_get_prices_from_json_rpc()
1178            .times(1)
1179            .returning(|| Box::pin(async { Ok(GasPrices::default()) }));
1180
1181        // Add the missing expectation for network
1182        mock_service
1183            .expect_network()
1184            .return_const(create_mock_evm_network("mainnet"));
1185
1186        let pc = PriceCalculator::new(mock_service, None);
1187        let relayer = create_mock_relayer();
1188        // Both max_fee_per_gas, max_priority_fee_per_gas, and gas_price absent
1189        let tx_data = EvmTransactionData {
1190            gas_price: None,
1191            max_fee_per_gas: None,
1192            max_priority_fee_per_gas: None,
1193            ..Default::default()
1194        };
1195
1196        let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1197        assert!(result.is_err());
1198        if let Err(TransactionError::InvalidType(msg)) = result {
1199            assert!(msg.contains("missing required gas price parameters"));
1200        } else {
1201            panic!("Expected InvalidType error");
1202        }
1203    }
1204
1205    #[tokio::test]
1206    async fn test_calculate_bumped_gas_price_capped() {
1207        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1208        let mock_prices = GasPrices {
1209            legacy_prices: SpeedPrices::default(),
1210            max_priority_fee_per_gas: SpeedPrices {
1211                safe_low: 4_000_000_000,
1212                average: 5_000_000_000,
1213                fast: 6_000_000_000,
1214                fastest: 8_000_000_000,
1215            },
1216            base_fee_per_gas: 100_000_000_000,
1217        };
1218        mock_service
1219            .expect_get_prices_from_json_rpc()
1220            .returning(move || {
1221                let prices = mock_prices.clone();
1222                Box::pin(async move { Ok(prices) })
1223            });
1224        mock_service
1225            .expect_network()
1226            .return_const(create_mock_evm_network("mainnet"));
1227
1228        let pc = PriceCalculator::new(mock_service, None);
1229        let mut relayer = create_mock_relayer();
1230        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1231            gas_price_cap: Some(105_000_000_000),
1232            ..Default::default()
1233        });
1234
1235        let tx_data = EvmTransactionData {
1236            max_fee_per_gas: Some(90_000_000_000),
1237            max_priority_fee_per_gas: Some(4_000_000_000),
1238            speed: Some(Speed::Fastest),
1239            ..Default::default()
1240        };
1241
1242        // Normally, we'd expect ~ (100 Gwei * 2.4) + 8 Gwei > 248 Gwei. We'll cap it at 105 Gwei.
1243        let bumped = pc
1244            .calculate_bumped_gas_price(&tx_data, &relayer)
1245            .await
1246            .unwrap();
1247        assert!(bumped.max_fee_per_gas.unwrap() <= 105_000_000_000);
1248        assert!(bumped.max_priority_fee_per_gas.unwrap() <= 105_000_000_000);
1249    }
1250
1251    #[tokio::test]
1252    async fn test_is_min_bumped_flag_eip1559() {
1253        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1254        let mock_prices = GasPrices {
1255            legacy_prices: SpeedPrices::default(),
1256            max_priority_fee_per_gas: SpeedPrices {
1257                safe_low: 1_000_000_000,
1258                average: 2_000_000_000,
1259                fast: 3_000_000_000,
1260                fastest: 4_000_000_000,
1261            },
1262            base_fee_per_gas: 40_000_000_000,
1263        };
1264        mock_service
1265            .expect_get_prices_from_json_rpc()
1266            .returning(move || {
1267                let prices = mock_prices.clone();
1268                Box::pin(async move { Ok(prices) })
1269            });
1270        mock_service
1271            .expect_network()
1272            .return_const(create_mock_evm_network("mainnet"));
1273
1274        let pc = PriceCalculator::new(mock_service, None);
1275        let mut relayer = create_mock_relayer();
1276
1277        // Case 1: Price high enough - should result in is_min_bumped = true
1278        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1279            gas_price_cap: Some(200_000_000_000u128),
1280            ..Default::default()
1281        });
1282
1283        let tx_data = EvmTransactionData {
1284            max_fee_per_gas: Some(50_000_000_000),
1285            max_priority_fee_per_gas: Some(2_000_000_000),
1286            speed: Some(Speed::Fast),
1287            ..Default::default()
1288        };
1289
1290        let bumped = pc
1291            .calculate_bumped_gas_price(&tx_data, &relayer)
1292            .await
1293            .unwrap();
1294        assert_eq!(
1295            bumped.is_min_bumped,
1296            Some(true),
1297            "Should be min bumped when prices are high enough"
1298        );
1299
1300        // Case 2: Gas price cap too low - should result in is_min_bumped = false
1301        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1302            gas_price_cap: Some(50_000_000_000u128), // Cap is below the min bump for max_fee_per_gas
1303            ..Default::default()
1304        });
1305
1306        let tx_data = EvmTransactionData {
1307            max_fee_per_gas: Some(50_000_000_000),
1308            max_priority_fee_per_gas: Some(2_000_000_000),
1309            speed: Some(Speed::Fast),
1310            ..Default::default()
1311        };
1312
1313        let bumped = pc
1314            .calculate_bumped_gas_price(&tx_data, &relayer)
1315            .await
1316            .unwrap();
1317        // Since min bump is 10%, original was 50 Gwei, min is 55 Gwei, but cap is 50 Gwei
1318        assert_eq!(
1319            bumped.is_min_bumped,
1320            Some(false),
1321            "Should not be min bumped when cap is too low"
1322        );
1323    }
1324
1325    #[tokio::test]
1326    async fn test_is_min_bumped_flag_legacy() {
1327        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1328        let mock_prices = GasPrices {
1329            legacy_prices: SpeedPrices {
1330                safe_low: 8_000_000_000,
1331                average: 10_000_000_000,
1332                fast: 12_000_000_000,
1333                fastest: 15_000_000_000,
1334            },
1335            max_priority_fee_per_gas: SpeedPrices::default(),
1336            base_fee_per_gas: 0,
1337        };
1338        mock_service
1339            .expect_get_prices_from_json_rpc()
1340            .returning(move || {
1341                let prices = mock_prices.clone();
1342                Box::pin(async move { Ok(prices) })
1343            });
1344        mock_service
1345            .expect_network()
1346            .return_const(create_mock_evm_network("mainnet"));
1347
1348        let pc = PriceCalculator::new(mock_service, None);
1349        let mut relayer = create_mock_relayer();
1350
1351        // Case 1: Regular case, cap is high enough
1352        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1353            gas_price_cap: Some(100_000_000_000u128),
1354            ..Default::default()
1355        });
1356
1357        let tx_data = EvmTransactionData {
1358            gas_price: Some(10_000_000_000),
1359            speed: Some(Speed::Fast),
1360            ..Default::default()
1361        };
1362
1363        let bumped = pc
1364            .calculate_bumped_gas_price(&tx_data, &relayer)
1365            .await
1366            .unwrap();
1367        assert_eq!(
1368            bumped.is_min_bumped,
1369            Some(true),
1370            "Should be min bumped with sufficient cap"
1371        );
1372
1373        // Case 2: Cap too low
1374        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1375            gas_price_cap: Some(10_000_000_000u128), // Same as original, preventing the 10% bump
1376            ..Default::default()
1377        });
1378
1379        let bumped = pc
1380            .calculate_bumped_gas_price(&tx_data, &relayer)
1381            .await
1382            .unwrap();
1383        assert_eq!(
1384            bumped.is_min_bumped,
1385            Some(false),
1386            "Should not be min bumped with insufficient cap"
1387        );
1388    }
1389
1390    #[tokio::test]
1391    async fn test_calculate_bumped_gas_price_with_extra_fee() {
1392        // Set up mock gas price service
1393        let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
1394        mock_gas_service
1395            .expect_get_prices_from_json_rpc()
1396            .returning(|| {
1397                Box::pin(async {
1398                    Ok(GasPrices {
1399                        legacy_prices: SpeedPrices {
1400                            safe_low: 10_000_000_000,
1401                            average: 12_000_000_000,
1402                            fast: 14_000_000_000,
1403                            fastest: 18_000_000_000,
1404                        },
1405                        max_priority_fee_per_gas: SpeedPrices::default(),
1406                        base_fee_per_gas: 40_000_000_000,
1407                    })
1408                })
1409            });
1410        mock_gas_service
1411            .expect_network()
1412            .return_const(create_mock_evm_network("mainnet"));
1413
1414        // Create a PriceCalculator without extra fee service first
1415        let pc = PriceCalculator::new(mock_gas_service, None);
1416
1417        // Create test transaction and relayer
1418        let relayer = create_mock_relayer();
1419        let tx_data = EvmTransactionData {
1420            max_fee_per_gas: Some(50_000_000_000),
1421            max_priority_fee_per_gas: Some(2_000_000_000),
1422            speed: Some(Speed::Fast),
1423            ..Default::default()
1424        };
1425
1426        // Call the method under test
1427        let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1428
1429        // Verify no extra fee when no overrider is used
1430        assert!(result.is_ok());
1431        let price_params = result.unwrap();
1432        assert_eq!(price_params.extra_fee, None);
1433    }
1434
1435    #[tokio::test]
1436    async fn test_total_cost_recomputed_without_overrider_legacy() {
1437        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1438        let mock_prices = GasPrices {
1439            legacy_prices: SpeedPrices {
1440                safe_low: 10_000_000_000,
1441                average: 12_000_000_000,
1442                fast: 14_000_000_000,
1443                fastest: 18_000_000_000,
1444            },
1445            max_priority_fee_per_gas: SpeedPrices::default(),
1446            base_fee_per_gas: 0,
1447        };
1448        mock_service
1449            .expect_get_prices_from_json_rpc()
1450            .returning(move || {
1451                let prices = mock_prices.clone();
1452                Box::pin(async move { Ok(prices) })
1453            });
1454        mock_service
1455            .expect_network()
1456            .return_const(create_mock_evm_network("mainnet"));
1457
1458        let pc = PriceCalculator::new(mock_service, None);
1459        let relayer = create_mock_relayer();
1460
1461        let gas_limit = 21_000u64;
1462        let tx_data = EvmTransactionData {
1463            gas_price: Some(20_000_000_000),
1464            gas_limit: Some(gas_limit),
1465            value: U256::ZERO,
1466            ..Default::default()
1467        };
1468
1469        let params = pc
1470            .calculate_bumped_gas_price(&tx_data, &relayer)
1471            .await
1472            .unwrap();
1473
1474        // Total cost should be recomputed using final gas_price and provided gas_limit
1475        let expected = U256::from(params.gas_price.unwrap()) * U256::from(gas_limit);
1476        assert_eq!(params.total_cost, expected);
1477    }
1478
1479    #[tokio::test]
1480    async fn test_total_cost_respected_with_overrider_nonzero_total_legacy() {
1481        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1482        let mock_prices = GasPrices {
1483            legacy_prices: SpeedPrices {
1484                safe_low: 10_000_000_000,
1485                average: 12_000_000_000,
1486                fast: 14_000_000_000,
1487                fastest: 18_000_000_000,
1488            },
1489            max_priority_fee_per_gas: SpeedPrices::default(),
1490            base_fee_per_gas: 0,
1491        };
1492        mock_service
1493            .expect_get_prices_from_json_rpc()
1494            .returning(move || {
1495                let prices = mock_prices.clone();
1496                Box::pin(async move { Ok(prices) })
1497            });
1498        mock_service
1499            .expect_network()
1500            .return_const(create_mock_evm_network("mainnet"));
1501
1502        let handler = Some(PriceParamsHandler::Mock(MockPriceHandler::new()));
1503        let pc = PriceCalculator::new(mock_service, handler);
1504        let relayer = create_mock_relayer();
1505
1506        let tx_data = EvmTransactionData {
1507            gas_price: Some(20_000_000_000),
1508            gas_limit: Some(21_000),
1509            value: U256::ZERO,
1510            ..Default::default()
1511        };
1512
1513        let params = pc
1514            .calculate_bumped_gas_price(&tx_data, &relayer)
1515            .await
1516            .unwrap();
1517
1518        // MockPriceHandler sets extra_fee = 42 and total_cost = 0 + 42.
1519        // Since total_cost is non-zero after the overrider, the calculator must not recompute it.
1520        assert_eq!(params.extra_fee, Some(U256::from(42u128)));
1521        assert_eq!(params.total_cost, U256::from(42u128));
1522    }
1523
1524    #[tokio::test]
1525    async fn test_get_transaction_price_params_with_mock_overrider_legacy() {
1526        use crate::services::gas::handlers::test_mock::MockPriceHandler;
1527
1528        let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
1529        mock_gas_service
1530            .expect_get_legacy_prices_from_json_rpc()
1531            .returning(|| Box::pin(async { Ok(SpeedPrices::default()) }));
1532        mock_gas_service
1533            .expect_network()
1534            .return_const(create_mock_evm_network("mainnet"));
1535
1536        let price_params_handler = Some(PriceParamsHandler::Mock(MockPriceHandler::new()));
1537        let pc = PriceCalculator::new(mock_gas_service, price_params_handler);
1538
1539        let relayer = create_mock_relayer();
1540        let tx_data = EvmTransactionData {
1541            gas_price: Some(20_000_000_000),
1542            ..Default::default()
1543        };
1544
1545        let params = pc
1546            .get_transaction_price_params(&tx_data, &relayer)
1547            .await
1548            .unwrap();
1549        assert_eq!(params.extra_fee, Some(U256::from(42u128)));
1550        assert!(params.total_cost >= U256::from(42u128));
1551    }
1552
1553    #[tokio::test]
1554    async fn test_get_transaction_price_params_with_mock_overrider_eip1559() {
1555        use crate::services::gas::handlers::test_mock::MockPriceHandler;
1556
1557        let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
1558        let mock_prices = GasPrices {
1559            legacy_prices: SpeedPrices::default(),
1560            max_priority_fee_per_gas: SpeedPrices::default(),
1561            base_fee_per_gas: 50_000_000_000,
1562        };
1563        mock_gas_service
1564            .expect_get_prices_from_json_rpc()
1565            .returning(move || {
1566                let prices = mock_prices.clone();
1567                Box::pin(async move { Ok(prices) })
1568            });
1569        mock_gas_service
1570            .expect_network()
1571            .return_const(create_mock_evm_network("mainnet"));
1572
1573        let price_params_handler = Some(PriceParamsHandler::Mock(MockPriceHandler::new()));
1574        let pc = PriceCalculator::new(mock_gas_service, price_params_handler);
1575
1576        let relayer = create_mock_relayer();
1577        let tx_data = EvmTransactionData {
1578            max_fee_per_gas: Some(30_000_000_000),
1579            max_priority_fee_per_gas: Some(2_000_000_000),
1580            ..Default::default()
1581        };
1582
1583        let params = pc
1584            .get_transaction_price_params(&tx_data, &relayer)
1585            .await
1586            .unwrap();
1587        assert_eq!(params.extra_fee, Some(U256::from(42u128)));
1588        assert!(params.total_cost >= U256::from(42u128));
1589    }
1590
1591    #[tokio::test]
1592    async fn test_get_transaction_price_params_recompute_without_overrider_legacy() {
1593        let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
1594        mock_gas_service
1595            .expect_get_legacy_prices_from_json_rpc()
1596            .returning(|| Box::pin(async { Ok(SpeedPrices::default()) }));
1597        mock_gas_service
1598            .expect_network()
1599            .return_const(create_mock_evm_network("mainnet"));
1600
1601        let pc = PriceCalculator::new(mock_gas_service, None);
1602        let relayer = create_mock_relayer();
1603
1604        let gas_limit = 21_000u64;
1605        let tx_data = EvmTransactionData {
1606            gas_price: Some(20_000_000_000),
1607            gas_limit: Some(gas_limit),
1608            value: U256::ZERO,
1609            ..Default::default()
1610        };
1611
1612        let params = pc
1613            .get_transaction_price_params(&tx_data, &relayer)
1614            .await
1615            .unwrap();
1616
1617        let expected = U256::from(params.gas_price.unwrap()) * U256::from(gas_limit);
1618        assert_eq!(params.total_cost, expected);
1619    }
1620
1621    #[test]
1622    fn test_calculate_total_cost_eip1559() {
1623        let price_params = PriceParams {
1624            gas_price: None,
1625            max_fee_per_gas: Some(30_000_000_000),
1626            max_priority_fee_per_gas: Some(2_000_000_000),
1627            is_min_bumped: None,
1628            extra_fee: None,
1629            total_cost: U256::ZERO,
1630        };
1631
1632        let gas_limit = 100_000;
1633        let value = U256::from(1_000_000_000_000_000_000u128); // 1 ETH
1634        let is_eip1559 = true;
1635
1636        let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1637
1638        // Expected: max_fee_per_gas * gas_limit + value
1639        let expected = U256::from(30_000_000_000u128) * U256::from(gas_limit) + value;
1640        assert_eq!(total_cost, expected);
1641    }
1642
1643    #[test]
1644    fn test_calculate_total_cost_legacy() {
1645        let price_params = PriceParams {
1646            gas_price: Some(20_000_000_000),
1647            max_fee_per_gas: None,
1648            max_priority_fee_per_gas: None,
1649            is_min_bumped: None,
1650            extra_fee: None,
1651            total_cost: U256::ZERO,
1652        };
1653
1654        let gas_limit = 100_000;
1655        let value = U256::from(1_000_000_000_000_000_000u128); // 1 ETH
1656        let is_eip1559 = false;
1657
1658        let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1659
1660        // Expected: gas_price * gas_limit + value
1661        let expected = U256::from(20_000_000_000u128) * U256::from(gas_limit) + value;
1662        assert_eq!(total_cost, expected);
1663    }
1664
1665    #[test]
1666    fn test_calculate_total_cost_with_extra_fee() {
1667        let price_params = PriceParams {
1668            gas_price: Some(20_000_000_000),
1669            max_fee_per_gas: None,
1670            max_priority_fee_per_gas: None,
1671            is_min_bumped: None,
1672            extra_fee: Some(U256::from(5_000_000_000u128)),
1673            total_cost: U256::ZERO,
1674        };
1675
1676        let gas_limit = 100_000;
1677        let value = U256::from(1_000_000_000_000_000_000u128); // 1 ETH
1678        let is_eip1559 = false;
1679
1680        let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1681
1682        let expected = U256::from(20_000_000_000u128) * U256::from(gas_limit)
1683            + value
1684            + U256::from(5_000_000_000u128);
1685        assert_eq!(total_cost, expected);
1686    }
1687
1688    #[test]
1689    fn test_calculate_total_cost_zero_values() {
1690        let price_params = PriceParams {
1691            gas_price: Some(0),
1692            max_fee_per_gas: Some(0),
1693            max_priority_fee_per_gas: Some(0),
1694            is_min_bumped: None,
1695            extra_fee: Some(U256::ZERO),
1696            total_cost: U256::ZERO,
1697        };
1698
1699        let gas_limit = 0;
1700        let value = U256::from(0);
1701
1702        let legacy_total_cost = price_params.calculate_total_cost(false, gas_limit, value);
1703        assert_eq!(legacy_total_cost, U256::ZERO);
1704
1705        let eip1559_total_cost = price_params.calculate_total_cost(true, gas_limit, value);
1706        assert_eq!(eip1559_total_cost, U256::ZERO);
1707    }
1708
1709    #[test]
1710    fn test_calculate_total_cost_missing_values() {
1711        let price_params = PriceParams {
1712            gas_price: None,
1713            max_fee_per_gas: None,
1714            max_priority_fee_per_gas: None,
1715            is_min_bumped: None,
1716            extra_fee: None,
1717            total_cost: U256::ZERO,
1718        };
1719
1720        let gas_limit = 100_000;
1721        let value = U256::from(1_000_000_000_000_000_000u128);
1722
1723        let legacy_total = price_params.calculate_total_cost(false, gas_limit, value);
1724        assert_eq!(legacy_total, value);
1725
1726        let eip1559_total = price_params.calculate_total_cost(true, gas_limit, value);
1727        assert_eq!(eip1559_total, value);
1728    }
1729
1730    #[test]
1731    fn test_calculate_min_bump_normal_cases() {
1732        let base_price = 20_000_000_000u128; // 20 Gwei
1733        let expected = 22_000_000_000u128; // 22 Gwei (10% bump)
1734        assert_eq!(calculate_min_bump(base_price), expected);
1735
1736        let base_price = 1_000_000_000u128;
1737        let expected = 1_100_000_000u128; // 1.1 Gwei
1738        assert_eq!(calculate_min_bump(base_price), expected);
1739
1740        let base_price = 100_000_000_000u128;
1741        let expected = 110_000_000_000u128; // 110 Gwei
1742        assert_eq!(calculate_min_bump(base_price), expected);
1743    }
1744
1745    #[test]
1746    fn test_calculate_min_bump_edge_cases() {
1747        // Test with zero - should return 1 wei (minimum bump)
1748        assert_eq!(calculate_min_bump(0), 1);
1749
1750        // Test with 1 wei - should return at least 2 wei
1751        let result = calculate_min_bump(1);
1752        assert!(result >= 2);
1753
1754        // Test with very small values where 10% bump rounds down to 0
1755        let base_price = 5u128; // 5 wei
1756        let result = calculate_min_bump(base_price);
1757        assert!(
1758            result > base_price,
1759            "Result {} should be greater than base_price {}",
1760            result,
1761            base_price
1762        );
1763
1764        let base_price = 9u128;
1765        let result = calculate_min_bump(base_price);
1766        assert_eq!(
1767            result, 10u128,
1768            "9 wei should bump to 10 wei (minimum 1 wei increase)"
1769        );
1770    }
1771
1772    #[test]
1773    fn test_calculate_min_bump_large_values() {
1774        // Test with large values to ensure no overflow
1775        let base_price = u128::MAX / 2;
1776        let result = calculate_min_bump(base_price);
1777        assert!(result > base_price);
1778
1779        // Test near maximum value
1780        let base_price = u128::MAX - 1000;
1781        let result = calculate_min_bump(base_price);
1782        // Should not panic and should return a reasonable value
1783        assert!(result >= base_price.saturating_add(1));
1784    }
1785
1786    #[test]
1787    fn test_calculate_min_bump_overflow_protection() {
1788        let base_price = u128::MAX;
1789        let result = calculate_min_bump(base_price);
1790        assert_eq!(result, u128::MAX);
1791
1792        let base_price = (u128::MAX / 11) * 10 + 1;
1793        let result = calculate_min_bump(base_price);
1794        assert!(result >= base_price);
1795    }
1796
1797    #[test]
1798    fn test_calculate_min_bump_minimum_increase_guarantee() {
1799        // Test that the function always increases by at least 1 wei
1800        let test_cases = vec![0, 1, 2, 5, 9, 10, 100, 1000, 10000];
1801
1802        for base_price in test_cases {
1803            let result = calculate_min_bump(base_price);
1804            assert!(
1805                result > base_price,
1806                "calculate_min_bump({}) = {} should be greater than base_price",
1807                base_price,
1808                result
1809            );
1810            assert!(
1811                result >= base_price.saturating_add(1),
1812                "calculate_min_bump({}) = {} should be at least base_price + 1",
1813                base_price,
1814                result
1815            );
1816        }
1817    }
1818
1819    #[tokio::test]
1820    async fn test_calculate_bumped_gas_price_no_mempool_network_eip1559() {
1821        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1822
1823        // Mock the network to return a no-mempool network
1824        mock_service
1825            .expect_network()
1826            .return_const(create_mock_no_mempool_network("arbitrum"));
1827
1828        // Mock get_prices_from_json_rpc for get_transaction_price_params call
1829        let mock_prices = GasPrices {
1830            legacy_prices: SpeedPrices {
1831                safe_low: 1_000_000_000,
1832                average: 2_000_000_000,
1833                fast: 3_000_000_000,
1834                fastest: 4_000_000_000,
1835            },
1836            max_priority_fee_per_gas: SpeedPrices {
1837                safe_low: 1_000_000_000,
1838                average: 2_000_000_000,
1839                fast: 3_000_000_000,
1840                fastest: 4_000_000_000,
1841            },
1842            base_fee_per_gas: 50_000_000_000,
1843        };
1844
1845        mock_service
1846            .expect_get_prices_from_json_rpc()
1847            .returning(move || {
1848                let prices = mock_prices.clone();
1849                Box::pin(async move { Ok(prices) })
1850            });
1851
1852        // Also mock get_legacy_prices_from_json_rpc in case it's called
1853        mock_service
1854            .expect_get_legacy_prices_from_json_rpc()
1855            .returning(|| {
1856                Box::pin(async {
1857                    Ok(SpeedPrices {
1858                        safe_low: 1_000_000_000,
1859                        average: 2_000_000_000,
1860                        fast: 3_000_000_000,
1861                        fastest: 4_000_000_000,
1862                    })
1863                })
1864            });
1865
1866        let pc = PriceCalculator::new(mock_service, None);
1867        let mut relayer = create_mock_relayer();
1868
1869        // Ensure relayer policy allows EIP1559 pricing
1870        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1871            eip1559_pricing: Some(true),
1872            ..Default::default()
1873        });
1874
1875        // Create a speed-based transaction that would normally be bumped
1876        let tx_data = EvmTransactionData {
1877            speed: Some(Speed::Fast),
1878            gas_limit: Some(21000),
1879            value: U256::from(1_000_000_000_000_000_000u128), // 1 ETH
1880            ..Default::default()
1881        };
1882
1883        let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1884
1885        assert!(result.is_ok());
1886        let price_params = result.unwrap();
1887
1888        // For no-mempool networks, should use current market prices, not bump
1889        assert_eq!(price_params.is_min_bumped, Some(true));
1890
1891        // The no-mempool network should use the current market prices instead of bumping
1892        // For this test, what matters is that it returns is_min_bumped: true
1893        // The exact pricing method depends on the network configuration
1894
1895        // Verify that some pricing was returned (either EIP1559 or legacy)
1896        assert!(
1897            price_params.max_priority_fee_per_gas.is_some() || price_params.gas_price.is_some()
1898        );
1899
1900        // Should have calculated total cost
1901        assert!(price_params.total_cost > U256::ZERO);
1902    }
1903
1904    #[tokio::test]
1905    async fn test_calculate_bumped_gas_price_no_mempool_network_legacy() {
1906        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1907
1908        // Mock the network to return a no-mempool network
1909        mock_service
1910            .expect_network()
1911            .return_const(create_mock_no_mempool_network("arbitrum"));
1912
1913        // Mock get_legacy_prices_from_json_rpc for get_transaction_price_params call
1914        let mock_legacy_prices = SpeedPrices {
1915            safe_low: 10_000_000_000,
1916            average: 12_000_000_000,
1917            fast: 14_000_000_000,
1918            fastest: 18_000_000_000,
1919        };
1920
1921        mock_service
1922            .expect_get_legacy_prices_from_json_rpc()
1923            .returning(move || {
1924                let prices = mock_legacy_prices.clone();
1925                Box::pin(async move { Ok(prices) })
1926            });
1927
1928        let pc = PriceCalculator::new(mock_service, None);
1929        let mut relayer = create_mock_relayer();
1930
1931        // Force legacy pricing
1932        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1933            eip1559_pricing: Some(false),
1934            ..Default::default()
1935        });
1936
1937        // Create a speed-based transaction that would normally be bumped
1938        let tx_data = EvmTransactionData {
1939            speed: Some(Speed::Fast),
1940            gas_limit: Some(21000),
1941            value: U256::from(1_000_000_000_000_000_000u128), // 1 ETH
1942            ..Default::default()
1943        };
1944
1945        let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1946
1947        assert!(result.is_ok());
1948        let price_params = result.unwrap();
1949
1950        // For no-mempool networks, should use current market prices, not bump
1951        assert_eq!(price_params.is_min_bumped, Some(true));
1952
1953        // Should return current market prices - verify that some pricing was returned
1954        assert!(
1955            price_params.max_priority_fee_per_gas.is_some() || price_params.gas_price.is_some()
1956        );
1957
1958        // Should have calculated total cost
1959        assert!(price_params.total_cost > U256::ZERO);
1960    }
1961
1962    #[tokio::test]
1963    async fn test_calculate_bumped_gas_price_no_mempool_vs_regular_network() {
1964        // Test EIP1559 transaction on regular network (should bump)
1965        let mut mock_service_regular = MockEvmGasPriceServiceTrait::new();
1966
1967        let mock_prices = GasPrices {
1968            legacy_prices: SpeedPrices::default(),
1969            max_priority_fee_per_gas: SpeedPrices {
1970                safe_low: 1_000_000_000,
1971                average: 2_000_000_000,
1972                fast: 3_000_000_000,
1973                fastest: 4_000_000_000,
1974            },
1975            base_fee_per_gas: 50_000_000_000,
1976        };
1977
1978        mock_service_regular
1979            .expect_network()
1980            .return_const(create_mock_evm_network("mainnet"));
1981
1982        let mock_prices_clone = mock_prices.clone();
1983        mock_service_regular
1984            .expect_get_prices_from_json_rpc()
1985            .returning(move || {
1986                let prices = mock_prices_clone.clone();
1987                Box::pin(async move { Ok(prices) })
1988            });
1989
1990        let pc_regular = PriceCalculator::new(mock_service_regular, None);
1991        let relayer = create_mock_relayer();
1992
1993        let tx_data = EvmTransactionData {
1994            speed: Some(Speed::Fast),
1995            ..Default::default()
1996        };
1997
1998        let result_regular = pc_regular
1999            .calculate_bumped_gas_price(&tx_data, &relayer)
2000            .await
2001            .unwrap();
2002
2003        // Regular network should return some pricing (either EIP1559 or legacy)
2004        assert!(
2005            result_regular.max_priority_fee_per_gas.is_some() || result_regular.gas_price.is_some()
2006        );
2007
2008        // Test same transaction on no-mempool network (should not bump)
2009        let mut mock_service_no_mempool = MockEvmGasPriceServiceTrait::new();
2010
2011        mock_service_no_mempool
2012            .expect_network()
2013            .return_const(create_mock_no_mempool_network("arbitrum"));
2014
2015        mock_service_no_mempool
2016            .expect_get_prices_from_json_rpc()
2017            .returning(move || {
2018                let prices = mock_prices.clone();
2019                Box::pin(async move { Ok(prices) })
2020            });
2021
2022        let pc_no_mempool = PriceCalculator::new(mock_service_no_mempool, None);
2023
2024        let result_no_mempool = pc_no_mempool
2025            .calculate_bumped_gas_price(&tx_data, &relayer)
2026            .await
2027            .unwrap();
2028
2029        // No-mempool network should use current market prices
2030        assert_eq!(result_no_mempool.is_min_bumped, Some(true));
2031
2032        // Both networks should return some pricing
2033        assert!(
2034            result_no_mempool.max_priority_fee_per_gas.is_some()
2035                || result_no_mempool.gas_price.is_some()
2036        );
2037
2038        // The key difference is that no-mempool networks should set is_min_bumped to true
2039        // Regular networks may or may not set is_min_bumped depending on the actual implementation
2040        assert_eq!(result_no_mempool.is_min_bumped, Some(true));
2041    }
2042
2043    #[tokio::test]
2044    async fn test_calculate_bumped_gas_price_arbitrum_network() {
2045        let mut mock_service = MockEvmGasPriceServiceTrait::new();
2046
2047        // Create a network that returns true for is_arbitrum() but not lacks_mempool()
2048        let arbitrum_network = EvmNetwork {
2049            network: "arbitrum-one".to_string(),
2050            rpc_urls: vec!["https://arb1.arbitrum.io/rpc".to_string()],
2051            explorer_urls: None,
2052            average_blocktime_ms: 1000, // 1 second for arbitrum
2053            is_testnet: false,
2054            tags: vec![ARBITRUM_BASED_TAG.to_string()], // This makes is_arbitrum() return true
2055            chain_id: 42161,
2056            required_confirmations: 1,
2057            features: vec!["eip1559".to_string()],
2058            symbol: "ETH".to_string(),
2059            gas_price_cache: None,
2060        };
2061
2062        // Mock the network to return our arbitrum network
2063        mock_service.expect_network().return_const(arbitrum_network);
2064
2065        // Mock get_prices_from_json_rpc for get_transaction_price_params call
2066        let mock_prices = GasPrices {
2067            legacy_prices: SpeedPrices {
2068                safe_low: 100_000_000, // 0.1 Gwei (typical for Arbitrum)
2069                average: 200_000_000,
2070                fast: 300_000_000,
2071                fastest: 400_000_000,
2072            },
2073            max_priority_fee_per_gas: SpeedPrices {
2074                safe_low: 10_000_000, // 0.01 Gwei
2075                average: 20_000_000,
2076                fast: 30_000_000,
2077                fastest: 40_000_000,
2078            },
2079            base_fee_per_gas: 100_000_000, // 0.1 Gwei
2080        };
2081
2082        mock_service
2083            .expect_get_prices_from_json_rpc()
2084            .returning(move || {
2085                let prices = mock_prices.clone();
2086                Box::pin(async move { Ok(prices) })
2087            });
2088
2089        let pc = PriceCalculator::new(mock_service, None);
2090        let relayer = create_mock_relayer();
2091
2092        // Create a speed-based transaction that would normally be bumped on regular networks
2093        let tx_data = EvmTransactionData {
2094            speed: Some(Speed::Fast),
2095            gas_limit: Some(21000),
2096            value: U256::from(1_000_000_000_000_000_000u128), // 1 ETH
2097            ..Default::default()
2098        };
2099
2100        let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
2101
2102        assert!(result.is_ok());
2103        let price_params = result.unwrap();
2104
2105        // For Arbitrum networks (is_arbitrum() == true), should skip bump and use current market prices
2106        assert_eq!(
2107            price_params.is_min_bumped,
2108            Some(true),
2109            "Arbitrum networks should skip bumping and use current market prices"
2110        );
2111
2112        // Should return pricing based on current market conditions, not bumped prices
2113        assert!(
2114            price_params.max_priority_fee_per_gas.is_some() || price_params.gas_price.is_some(),
2115            "Should return some form of pricing"
2116        );
2117
2118        // Should have calculated total cost
2119        assert!(
2120            price_params.total_cost > U256::ZERO,
2121            "Should have non-zero total cost"
2122        );
2123
2124        // Verify that the prices returned are based on current market (Speed::Fast)
2125        // and not bumped versions of existing transaction prices
2126        if let Some(priority_fee) = price_params.max_priority_fee_per_gas {
2127            // For Speed::Fast, should be around 30_000_000 (0.03 Gwei) based on our mock
2128            assert!(
2129                priority_fee <= 50_000_000, // Should be reasonable for current market, not a bump
2130                "Priority fee should be based on current market, not bumped: {}",
2131                priority_fee
2132            );
2133        }
2134    }
2135}