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