openzeppelin_relayer/domain/transaction/evm/
price_calculator.rs

1//! Gas price calculation module for Ethereum transactions.
2//!
3//! This module provides functionality for calculating gas prices for different types of Ethereum transactions:
4//! - Legacy transactions (using `gas_price`)
5//! - EIP1559 transactions (using `max_fee_per_gas` and `max_priority_fee_per_gas`)
6//! - Speed-based transactions (automatically choosing between legacy and EIP1559 based on network support)
7//!
8//! The module implements various pricing strategies and safety mechanisms:
9//! - Gas price caps to protect against excessive fees
10//! - Dynamic base fee calculations for EIP1559 transactions
11//! - Speed-based multipliers for different transaction priorities (SafeLow, Average, Fast, Fastest)
12//! - Network-specific block time considerations for fee estimations
13//!
14//! # Example
15//! ```rust, ignore
16//! # use your_crate::{PriceCalculator, EvmTransactionData, RelayerRepoModel, EvmGasPriceService};
17//! # async fn example<P: EvmProviderTrait>(
18//! #     tx_data: &EvmTransactionData,
19//! #     relayer: &RelayerRepoModel,
20//! #     gas_price_service: &EvmGasPriceService<P>,
21//! #     provider: &P
22//! # ) -> Result<(), TransactionError> {
23//! let price_params = PriceCalculator::get_transaction_price_params(
24//!     tx_data,
25//!     relayer,
26//!     gas_price_service,
27//!     provider
28//! ).await?;
29//! # Ok(())
30//! # }
31//! ```
32//!
33//! The module uses EIP1559-specific constants for calculating appropriate gas fees:
34//! - Base fee increase factor: 12.5% per block
35//! - Maximum base fee multiplier: 10x
36//! - Time window for fee calculation: 90 seconds
37use crate::{
38    constants::DEFAULT_TRANSACTION_SPEED,
39    models::{
40        evm::Speed, EvmNetwork, EvmTransactionData, EvmTransactionDataTrait, RelayerRepoModel,
41        TransactionError, U256,
42    },
43    services::{
44        gas::{EvmGasPriceServiceTrait, NetworkExtraFeeCalculatorServiceTrait},
45        GasPrices, NetworkExtraFeeCalculator,
46    },
47};
48
49#[cfg(test)]
50use mockall::automock;
51
52#[cfg(test)]
53use crate::services::gas::MockNetworkExtraFeeCalculatorServiceTrait;
54
55#[async_trait::async_trait]
56#[cfg_attr(test, automock)]
57pub trait PriceCalculatorTrait: Send + Sync {
58    async fn get_transaction_price_params(
59        &self,
60        tx_data: &EvmTransactionData,
61        relayer: &RelayerRepoModel,
62    ) -> Result<PriceParams, TransactionError>;
63
64    async fn calculate_bumped_gas_price(
65        &self,
66        tx_data: &EvmTransactionData,
67        relayer: &RelayerRepoModel,
68    ) -> Result<PriceParams, TransactionError>;
69}
70
71type GasPriceCapResult = (Option<u128>, Option<u128>, Option<u128>);
72
73const PRECISION: u128 = 1_000_000_000; // 10^9 (similar to Gwei)
74const MINUTE_AND_HALF_MS: u128 = 90000;
75const BASE_FEE_INCREASE_FACTOR_PERCENT: u128 = 125; // 12.5% increase per block (as percentage * 10)
76const MAX_BASE_FEE_MULTIPLIER: u128 = 10 * PRECISION; // 10.0 * PRECISION
77
78#[derive(Debug, Clone)]
79pub struct PriceParams {
80    pub gas_price: Option<u128>,
81    pub max_fee_per_gas: Option<u128>,
82    pub max_priority_fee_per_gas: Option<u128>,
83    pub is_min_bumped: Option<bool>,
84    pub extra_fee: Option<u128>,
85    pub total_cost: U256,
86}
87
88impl PriceParams {
89    pub fn calculate_total_cost(&self, is_eip1559: bool, gas_limit: u64, value: U256) -> U256 {
90        match is_eip1559 {
91            true => {
92                U256::from(self.max_fee_per_gas.unwrap_or(0)) * U256::from(gas_limit)
93                    + value
94                    + U256::from(self.extra_fee.unwrap_or(0))
95            }
96            false => {
97                U256::from(self.gas_price.unwrap_or(0)) * U256::from(gas_limit)
98                    + value
99                    + U256::from(self.extra_fee.unwrap_or(0))
100            }
101        }
102    }
103}
104
105/// Safely calculates the minimum required gas price for a replacement transaction.
106/// Uses saturating arithmetic to prevent overflow and maintains precision.
107///
108/// # Arguments
109///
110/// * `base_price` - The original gas price to calculate bump from
111///
112/// # Returns
113///
114/// The minimum required price for replacement, or `u128::MAX` if overflow would occur.
115pub fn calculate_min_bump(base_price: u128) -> u128 {
116    // Convert MIN_BUMP_FACTOR to a rational representation to avoid floating point precision issues
117    // MIN_BUMP_FACTOR = 1.1 = 11/10
118    const BUMP_NUMERATOR: u128 = 11;
119    const BUMP_DENOMINATOR: u128 = 10;
120
121    let bumped_price = base_price
122        .saturating_mul(BUMP_NUMERATOR)
123        .saturating_div(BUMP_DENOMINATOR);
124
125    // Ensure we always bump by at least 1 wei to guarantee replacement
126    std::cmp::max(bumped_price, base_price.saturating_add(1))
127}
128
129/// Primary struct for calculating gas prices with an injected `EvmGasPriceServiceTrait`.
130pub struct PriceCalculator<G: EvmGasPriceServiceTrait> {
131    gas_price_service: G,
132    network_extra_fee_calculator_service: NetworkExtraFeeCalculator<G::Provider>,
133}
134
135#[async_trait::async_trait]
136impl<G: EvmGasPriceServiceTrait + Send + Sync> PriceCalculatorTrait for PriceCalculator<G> {
137    async fn get_transaction_price_params(
138        &self,
139        tx_data: &EvmTransactionData,
140        relayer: &RelayerRepoModel,
141    ) -> Result<PriceParams, TransactionError> {
142        self.get_transaction_price_params(tx_data, relayer).await
143    }
144
145    async fn calculate_bumped_gas_price(
146        &self,
147        tx_data: &EvmTransactionData,
148        relayer: &RelayerRepoModel,
149    ) -> Result<PriceParams, TransactionError> {
150        self.calculate_bumped_gas_price(tx_data, relayer).await
151    }
152}
153
154impl<G: EvmGasPriceServiceTrait> PriceCalculator<G> {
155    pub fn new(
156        gas_price_service: G,
157        network_extra_fee_calculator_service: NetworkExtraFeeCalculator<G::Provider>,
158    ) -> Self {
159        Self {
160            gas_price_service,
161            network_extra_fee_calculator_service,
162        }
163    }
164
165    /// Calculates transaction price parameters based on the transaction type and network conditions.
166    ///
167    /// This function determines the appropriate gas pricing strategy based on the transaction type:
168    /// - For legacy transactions: calculates gas_price
169    /// - For EIP1559 transactions: calculates max_fee_per_gas and max_priority_fee_per_gas
170    /// - For speed-based transactions: automatically chooses between legacy and EIP1559 based on network support
171    ///
172    /// # Arguments
173    /// * `tx_data` - Transaction data containing type and pricing information
174    /// * `relayer` - Relayer configuration including pricing policies and caps
175    /// * `gas_price_service` - Service for fetching current gas prices from the network
176    /// * `provider` - Network provider for accessing blockchain data
177    ///
178    /// # Returns
179    /// * `Result<PriceParams, TransactionError>` - Calculated price parameters or error
180    pub async fn get_transaction_price_params(
181        &self,
182        tx_data: &EvmTransactionData,
183        relayer: &RelayerRepoModel,
184    ) -> Result<PriceParams, TransactionError> {
185        let price_params = self
186            .fetch_price_params_based_on_tx_type(tx_data, relayer)
187            .await?;
188        let (gas_price_capped, max_fee_per_gas_capped, max_priority_fee_per_gas_capped) = self
189            .apply_gas_price_cap(
190                price_params.gas_price.unwrap_or_default(),
191                price_params.max_fee_per_gas,
192                price_params.max_priority_fee_per_gas,
193                relayer,
194            )?;
195
196        let mut final_params = PriceParams {
197            gas_price: gas_price_capped,
198            max_fee_per_gas: max_fee_per_gas_capped,
199            max_priority_fee_per_gas: max_priority_fee_per_gas_capped,
200            is_min_bumped: None,
201            extra_fee: None,
202            total_cost: U256::ZERO,
203        };
204
205        match &self.network_extra_fee_calculator_service {
206            NetworkExtraFeeCalculator::None => {}
207            _ => {
208                let new_tx = tx_data.clone().with_price_params(final_params.clone());
209                let extra_fee = self
210                    .network_extra_fee_calculator_service
211                    .get_extra_fee(&new_tx)
212                    .await?;
213                final_params.extra_fee = Some(extra_fee.try_into().unwrap_or(0));
214            }
215        }
216
217        final_params.total_cost = final_params.calculate_total_cost(
218            tx_data.is_eip1559(),
219            tx_data.gas_limit,
220            U256::from(tx_data.value),
221        );
222
223        Ok(final_params)
224    }
225
226    /// Computes bumped gas price for transaction resubmission, factoring in network conditions.
227    ///
228    /// This refactor breaks the logic into smaller helper functions for clarity and testability.
229    /// Each helper is commented to show how the final gas parameters are derived.
230    ///
231    /// 1. Determine if the transaction is EIP1559 or Legacy.
232    /// 2. Calculate minimum bump requirements (e.g., +10%).
233    /// 3. Compare with current network prices to decide how much to bump.
234    /// 4. Apply any relayer gas price caps.
235    /// 5. Return the final bumped gas parameters.
236    ///
237    /// The returned PriceParams includes an is_min_bumped flag that indicates whether
238    /// the calculated gas parameters meet the minimum bump requirements.
239    pub async fn calculate_bumped_gas_price(
240        &self,
241        tx_data: &EvmTransactionData,
242        relayer: &RelayerRepoModel,
243    ) -> Result<PriceParams, TransactionError> {
244        let network_gas_prices = self.gas_price_service.get_prices_from_json_rpc().await?;
245        let relayer_gas_price_cap = relayer
246            .policies
247            .get_evm_policy()
248            .gas_price_cap
249            .unwrap_or(u128::MAX);
250
251        // Decide EIP1559 vs Legacy based on presence of maxFeePerGas / maxPriorityFeePerGas vs gasPrice
252        let bumped_price_params = match (
253            tx_data.max_fee_per_gas,
254            tx_data.max_priority_fee_per_gas,
255            tx_data.gas_price,
256        ) {
257            (Some(max_fee), Some(max_priority_fee), _) => {
258                // EIP1559
259                self.handle_eip1559_bump(
260                    &network_gas_prices,
261                    relayer_gas_price_cap,
262                    tx_data.speed.as_ref(),
263                    max_fee,
264                    max_priority_fee,
265                )?
266            }
267            (None, None, Some(gas_price)) => {
268                // Legacy
269                self.handle_legacy_bump(
270                    &network_gas_prices,
271                    relayer_gas_price_cap,
272                    tx_data.speed.as_ref(),
273                    gas_price,
274                )?
275            }
276            _ => {
277                return Err(TransactionError::InvalidType(
278                    "Transaction missing required gas price parameters".to_string(),
279                ))
280            }
281        };
282
283        // Add extra fee if needed
284        let mut final_params = bumped_price_params;
285        let value = tx_data.value;
286        let gas_limit = tx_data.gas_limit;
287        let is_eip1559 = tx_data.is_eip1559();
288
289        match &self.network_extra_fee_calculator_service {
290            NetworkExtraFeeCalculator::None => {}
291            _ => {
292                let new_tx = tx_data.clone().with_price_params(final_params.clone());
293                let extra_fee = self
294                    .network_extra_fee_calculator_service
295                    .get_extra_fee(&new_tx)
296                    .await?;
297                final_params.extra_fee = Some(extra_fee.try_into().unwrap_or(0));
298            }
299        }
300
301        final_params.total_cost =
302            final_params.calculate_total_cost(is_eip1559, gas_limit, U256::from(value));
303
304        Ok(final_params)
305    }
306
307    /// Computes the bumped gas parameters for an EIP-1559 transaction resubmission.
308    ///
309    /// The function performs the following steps:
310    /// 1. Computes the minimum required fee values by increasing the previous fees by 10%.
311    /// 2. Retrieves the current network market priority fee for the transaction's speed.
312    /// 3. Chooses the new priority fee as either the current market fee (if it meets the 10% increase)
313    ///    or the calculated minimum bump.
314    /// 4. Computes the new maximum fee using two approaches:
315    ///    - Method A: Uses the current base fee, ensuring it meets the minimum bumped max fee.
316    ///    - Method B: Computes a recommended max fee based on a network-specific multiplier plus the new priority fee.
317    ///      The higher value between these two methods is chosen.
318    /// 5. Applies the relayer's gas price cap to both the new priority fee and the new max fee.
319    /// 6. Returns the final capped gas parameters.
320    ///
321    /// Note: All fee values are expected to be in Wei.
322    fn handle_eip1559_bump(
323        &self,
324        network_gas_prices: &GasPrices,
325        gas_price_cap: u128,
326        maybe_speed: Option<&Speed>,
327        max_fee: u128,
328        max_priority_fee: u128,
329    ) -> Result<PriceParams, TransactionError> {
330        let speed = maybe_speed.unwrap_or(&DEFAULT_TRANSACTION_SPEED);
331
332        // Calculate the minimum required fees (10% increase over previous values)
333        let min_bump_max_fee = calculate_min_bump(max_fee);
334        let min_bump_max_priority = calculate_min_bump(max_priority_fee);
335
336        // Get the current market priority fee for the given speed.
337        let current_market_priority =
338            Self::get_market_price_for_speed(network_gas_prices, true, speed);
339
340        // Determine the new maxPriorityFeePerGas:
341        // Use the current market fee if it is at least the minimum bumped fee,
342        // otherwise use the minimum bumped priority fee.
343        let bumped_priority_fee = if current_market_priority >= min_bump_max_priority {
344            current_market_priority
345        } else {
346            min_bump_max_priority
347        };
348
349        // Compute the new maxFeePerGas using two methods:
350        // Method A: Use the current base fee, but ensure it is not lower than the minimum bumped max fee.
351        let base_fee_wei = network_gas_prices.base_fee_per_gas;
352        let bumped_max_fee_per_gas = if base_fee_wei >= min_bump_max_fee {
353            base_fee_wei
354        } else {
355            min_bump_max_fee
356        };
357
358        // Method B: Calculate a recommended max fee based on the base fee multiplied by a network factor,
359        // plus the new priority fee.
360        let recommended_max_fee_per_gas = calculate_max_fee_per_gas(
361            base_fee_wei,
362            bumped_priority_fee,
363            self.gas_price_service.network(),
364        );
365
366        // Choose the higher value from the two methods to be competitive under current network conditions.
367        let final_max_fee = std::cmp::max(bumped_max_fee_per_gas, recommended_max_fee_per_gas);
368
369        // Step 5: Apply the gas price cap to both the new priority fee and the new max fee.
370        let capped_priority = Self::cap_gas_price(bumped_priority_fee, gas_price_cap);
371        let capped_max_fee = Self::cap_gas_price(final_max_fee, gas_price_cap);
372
373        // Check if the capped values still meet the minimum bump requirements
374        let is_min_bumped =
375            capped_priority >= min_bump_max_priority && capped_max_fee >= min_bump_max_fee;
376
377        // Step 6: Return the final bumped gas parameters.
378        Ok(PriceParams {
379            gas_price: None,
380            max_priority_fee_per_gas: Some(capped_priority),
381            max_fee_per_gas: Some(capped_max_fee),
382            is_min_bumped: Some(is_min_bumped),
383            extra_fee: None,
384            total_cost: U256::ZERO,
385        })
386    }
387
388    /// Handle Legacy bump logic:
389    /// 1) Calculate min bump for gasPrice.
390    /// 2) Compare with current market price for the given speed.
391    /// 3) Apply final caps.
392    fn handle_legacy_bump(
393        &self,
394        network_gas_prices: &GasPrices,
395        gas_price_cap: u128,
396        maybe_speed: Option<&Speed>,
397        gas_price: u128,
398    ) -> Result<PriceParams, TransactionError> {
399        let speed = maybe_speed.unwrap_or(&Speed::Fast);
400
401        // Minimum bump
402        let min_bump_gas_price = calculate_min_bump(gas_price);
403
404        // Current market gas price for chosen speed
405        let current_market_price =
406            Self::get_market_price_for_speed(network_gas_prices, false, speed);
407
408        let bumped_gas_price = if current_market_price >= min_bump_gas_price {
409            current_market_price
410        } else {
411            min_bump_gas_price
412        };
413
414        // Cap
415        let capped_gas_price = Self::cap_gas_price(bumped_gas_price, gas_price_cap);
416
417        // Check if the capped value still meets the minimum bump requirement
418        let is_min_bumped = capped_gas_price >= min_bump_gas_price;
419
420        Ok(PriceParams {
421            gas_price: Some(capped_gas_price),
422            max_priority_fee_per_gas: None,
423            max_fee_per_gas: None,
424            is_min_bumped: Some(is_min_bumped),
425            extra_fee: None,
426            total_cost: U256::ZERO,
427        })
428    }
429    /// Fetches price params based on the type of transaction (legacy, EIP1559, speed-based).
430    async fn fetch_price_params_based_on_tx_type(
431        &self,
432        tx_data: &EvmTransactionData,
433        relayer: &RelayerRepoModel,
434    ) -> Result<PriceParams, TransactionError> {
435        if tx_data.is_legacy() {
436            self.fetch_legacy_price_params(tx_data)
437        } else if tx_data.is_eip1559() {
438            self.fetch_eip1559_price_params(tx_data)
439        } else if tx_data.is_speed() {
440            self.fetch_speed_price_params(tx_data, relayer).await
441        } else {
442            Err(TransactionError::NotSupported(
443                "Invalid transaction type".to_string(),
444            ))
445        }
446    }
447
448    /// Handles gas price calculation for legacy transactions.
449    ///
450    /// # Arguments
451    /// * `tx_data` - Transaction data containing the gas price
452    ///
453    /// # Returns
454    /// * `Result<PriceParams, TransactionError>` - Price parameters for legacy transaction
455    fn fetch_legacy_price_params(
456        &self,
457        tx_data: &EvmTransactionData,
458    ) -> Result<PriceParams, TransactionError> {
459        let gas_price = tx_data.gas_price.ok_or(TransactionError::NotSupported(
460            "Gas price is required for legacy transactions".to_string(),
461        ))?;
462        Ok(PriceParams {
463            gas_price: Some(gas_price),
464            max_fee_per_gas: None,
465            max_priority_fee_per_gas: None,
466            is_min_bumped: None,
467            extra_fee: None,
468            total_cost: U256::ZERO,
469        })
470    }
471
472    fn fetch_eip1559_price_params(
473        &self,
474        tx_data: &EvmTransactionData,
475    ) -> Result<PriceParams, TransactionError> {
476        let max_fee = tx_data
477            .max_fee_per_gas
478            .ok_or(TransactionError::NotSupported(
479                "Max fee per gas is required for EIP1559 transactions".to_string(),
480            ))?;
481        let max_priority_fee =
482            tx_data
483                .max_priority_fee_per_gas
484                .ok_or(TransactionError::NotSupported(
485                    "Max priority fee per gas is required for EIP1559 transactions".to_string(),
486                ))?;
487        Ok(PriceParams {
488            gas_price: None,
489            max_fee_per_gas: Some(max_fee),
490            max_priority_fee_per_gas: Some(max_priority_fee),
491            is_min_bumped: None,
492            extra_fee: None,
493            total_cost: U256::ZERO,
494        })
495    }
496    /// Handles gas price calculation for speed-based transactions.
497    ///
498    /// Determines whether to use legacy or EIP1559 pricing based on network configuration
499    /// and calculates appropriate gas prices based on the requested speed.
500    async fn fetch_speed_price_params(
501        &self,
502        tx_data: &EvmTransactionData,
503        relayer: &RelayerRepoModel,
504    ) -> Result<PriceParams, TransactionError> {
505        let speed = tx_data
506            .speed
507            .as_ref()
508            .ok_or(TransactionError::NotSupported(
509                "Speed is required".to_string(),
510            ))?;
511        let use_legacy = relayer.policies.get_evm_policy().eip1559_pricing == Some(false)
512            || self.gas_price_service.network().is_legacy();
513
514        if use_legacy {
515            self.fetch_legacy_speed_params(speed).await
516        } else {
517            self.fetch_eip1559_speed_params(speed).await
518        }
519    }
520
521    /// Calculates EIP1559 gas prices based on the requested speed.
522    ///
523    /// Uses the gas price service to fetch current network conditions and calculates
524    /// appropriate max fee and priority fee based on the speed setting.
525    async fn fetch_eip1559_speed_params(
526        &self,
527        speed: &Speed,
528    ) -> Result<PriceParams, TransactionError> {
529        let prices = self.gas_price_service.get_prices_from_json_rpc().await?;
530        let priority_fee = match speed {
531            Speed::SafeLow => prices.max_priority_fee_per_gas.safe_low,
532            Speed::Average => prices.max_priority_fee_per_gas.average,
533            Speed::Fast => prices.max_priority_fee_per_gas.fast,
534            Speed::Fastest => prices.max_priority_fee_per_gas.fastest,
535        };
536        let max_fee = calculate_max_fee_per_gas(
537            prices.base_fee_per_gas,
538            priority_fee,
539            self.gas_price_service.network(),
540        );
541        Ok(PriceParams {
542            gas_price: None,
543            max_fee_per_gas: Some(max_fee),
544            max_priority_fee_per_gas: Some(priority_fee),
545            is_min_bumped: None,
546            extra_fee: None,
547            total_cost: U256::ZERO,
548        })
549    }
550    /// Calculates legacy gas prices based on the requested speed.
551    ///
552    /// Uses the gas price service to fetch current gas prices and applies
553    /// speed-based multipliers for legacy transactions.
554    async fn fetch_legacy_speed_params(
555        &self,
556        speed: &Speed,
557    ) -> Result<PriceParams, TransactionError> {
558        let prices = self
559            .gas_price_service
560            .get_legacy_prices_from_json_rpc()
561            .await?;
562        let gas_price = match speed {
563            Speed::SafeLow => prices.safe_low,
564            Speed::Average => prices.average,
565            Speed::Fast => prices.fast,
566            Speed::Fastest => prices.fastest,
567        };
568        Ok(PriceParams {
569            gas_price: Some(gas_price),
570            max_fee_per_gas: None,
571            max_priority_fee_per_gas: None,
572            is_min_bumped: None,
573            extra_fee: None,
574            total_cost: U256::ZERO,
575        })
576    }
577
578    /// Applies gas price caps to the calculated prices.
579    ///
580    /// Ensures that gas prices don't exceed the configured maximum limits and
581    /// maintains proper relationships between different price parameters.
582    fn apply_gas_price_cap(
583        &self,
584        gas_price: u128,
585        max_fee_per_gas: Option<u128>,
586        max_priority_fee_per_gas: Option<u128>,
587        relayer: &RelayerRepoModel,
588    ) -> Result<GasPriceCapResult, TransactionError> {
589        let gas_price_cap = relayer
590            .policies
591            .get_evm_policy()
592            .gas_price_cap
593            .unwrap_or(u128::MAX);
594
595        if let (Some(max_fee), Some(max_priority)) = (max_fee_per_gas, max_priority_fee_per_gas) {
596            // Cap the maxFeePerGas
597            let capped_max_fee = Self::cap_gas_price(max_fee, gas_price_cap);
598
599            // Ensure maxPriorityFeePerGas < maxFeePerGas to avoid client errors
600            let capped_max_priority = Self::cap_gas_price(max_priority, capped_max_fee);
601            Ok((None, Some(capped_max_fee), Some(capped_max_priority)))
602        } else {
603            // Handle legacy transaction
604            Ok((
605                Some(Self::cap_gas_price(gas_price, gas_price_cap)),
606                None,
607                None,
608            ))
609        }
610    }
611
612    fn cap_gas_price(price: u128, cap: u128) -> u128 {
613        std::cmp::min(price, cap)
614    }
615
616    /// Returns the market price for the given speed. If `is_eip1559` is true, use `max_priority_fee_per_gas`,
617    /// otherwise use `legacy_prices`.
618    fn get_market_price_for_speed(prices: &GasPrices, is_eip1559: bool, speed: &Speed) -> u128 {
619        if is_eip1559 {
620            match speed {
621                Speed::SafeLow => prices.max_priority_fee_per_gas.safe_low,
622                Speed::Average => prices.max_priority_fee_per_gas.average,
623                Speed::Fast => prices.max_priority_fee_per_gas.fast,
624                Speed::Fastest => prices.max_priority_fee_per_gas.fastest,
625            }
626        } else {
627            match speed {
628                Speed::SafeLow => prices.legacy_prices.safe_low,
629                Speed::Average => prices.legacy_prices.average,
630                Speed::Fast => prices.legacy_prices.fast,
631                Speed::Fastest => prices.legacy_prices.fastest,
632            }
633        }
634    }
635}
636
637fn get_base_fee_multiplier(network: &EvmNetwork) -> u128 {
638    let block_interval_ms = network.average_blocktime().map(|d| d.as_millis()).unwrap();
639
640    // Calculate number of blocks (as integer)
641    let n_blocks_int = MINUTE_AND_HALF_MS / block_interval_ms;
642
643    // Calculate number of blocks (fractional part in thousandths)
644    let n_blocks_frac = ((MINUTE_AND_HALF_MS % block_interval_ms) * 1000) / block_interval_ms;
645
646    // Calculate multiplier using compound interest formula: (1 + r)^n
647    // For integer part: (1 + 0.125)^n_blocks_int
648    let mut multiplier = PRECISION;
649
650    // Calculate (1.125)^n_blocks_int using repeated multiplication
651    for _ in 0..n_blocks_int {
652        multiplier = (multiplier
653            * (PRECISION + (PRECISION * BASE_FEE_INCREASE_FACTOR_PERCENT) / 1000))
654            / PRECISION;
655    }
656
657    // Handle fractional part with linear approximation
658    // For fractional part: approximately 1 + (fraction * 0.125)
659    if n_blocks_frac > 0 {
660        let frac_increase =
661            (n_blocks_frac * BASE_FEE_INCREASE_FACTOR_PERCENT * PRECISION) / (1000 * 1000);
662        multiplier = (multiplier * (PRECISION + frac_increase)) / PRECISION;
663    }
664
665    // Apply maximum cap
666    std::cmp::min(multiplier, MAX_BASE_FEE_MULTIPLIER)
667}
668
669/// Calculate max fee per gas for EIP1559 transactions (all values in wei)
670fn calculate_max_fee_per_gas(
671    base_fee_wei: u128,
672    max_priority_fee_wei: u128,
673    network: &EvmNetwork,
674) -> u128 {
675    // Get multiplier in fixed-point format
676    let multiplier = get_base_fee_multiplier(network);
677
678    // Multiply base fee by multiplier (with proper scaling)
679    let multiplied_base_fee = (base_fee_wei * multiplier) / PRECISION;
680
681    // Add priority fee
682    multiplied_base_fee + max_priority_fee_wei
683}
684#[cfg(test)]
685mod tests {
686    use super::*;
687    use crate::models::{
688        evm::Speed, EvmNetwork, EvmTransactionData, NetworkType, RelayerEvmPolicy,
689        RelayerNetworkPolicy, RelayerRepoModel, U256,
690    };
691    use crate::services::{
692        EvmGasPriceService, GasPrices, MockEvmGasPriceServiceTrait, MockEvmProviderTrait,
693        SpeedPrices,
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        EvmNetwork {
704            network: name.to_string(),
705            rpc_urls: vec!["https://rpc.example.com".to_string()],
706            explorer_urls: None,
707            average_blocktime_ms,
708            is_testnet: true,
709            tags: vec![],
710            chain_id: 1337,
711            required_confirmations: 1,
712            features: vec![],
713            symbol: "ETH".to_string(),
714        }
715    }
716
717    fn create_mock_relayer() -> RelayerRepoModel {
718        RelayerRepoModel {
719            id: "test-relayer".to_string(),
720            name: "Test Relayer".to_string(),
721            network: "mainnet".to_string(),
722            network_type: NetworkType::Evm,
723            address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string(),
724            policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy::default()),
725            paused: false,
726            notification_id: None,
727            signer_id: "test-signer".to_string(),
728            system_disabled: false,
729            custom_rpc_urls: None,
730        }
731    }
732
733    #[tokio::test]
734    async fn test_legacy_transaction() {
735        let mut provider = MockEvmProviderTrait::new();
736        provider
737            .expect_get_balance()
738            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
739
740        let relayer = create_mock_relayer();
741        let gas_price_service =
742            EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"));
743
744        let tx_data = EvmTransactionData {
745            gas_price: Some(20000000000),
746            ..Default::default()
747        };
748
749        let mut provider = MockEvmProviderTrait::new();
750        provider
751            .expect_get_balance()
752            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
753
754        // Create the PriceCalculator with the gas_price_service
755        let pc = PriceCalculator::new(gas_price_service, NetworkExtraFeeCalculator::None);
756
757        let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
758        assert!(result.is_ok());
759        let params = result.unwrap();
760        assert_eq!(params.gas_price, Some(20000000000));
761        assert!(params.max_fee_per_gas.is_none());
762        assert!(params.max_priority_fee_per_gas.is_none());
763    }
764
765    #[tokio::test]
766    async fn test_eip1559_transaction() {
767        let mut provider = MockEvmProviderTrait::new();
768        provider
769            .expect_get_balance()
770            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
771
772        let relayer = create_mock_relayer();
773        let gas_price_service =
774            EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"));
775
776        let tx_data = EvmTransactionData {
777            gas_price: None,
778            max_fee_per_gas: Some(30000000000),
779            max_priority_fee_per_gas: Some(2000000000),
780            ..Default::default()
781        };
782
783        let mut provider = MockEvmProviderTrait::new();
784        provider
785            .expect_get_balance()
786            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
787
788        // Create the PriceCalculator
789        let pc = PriceCalculator::new(gas_price_service, NetworkExtraFeeCalculator::None);
790
791        let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
792        assert!(result.is_ok());
793        let params = result.unwrap();
794        assert!(params.gas_price.is_none());
795        assert_eq!(params.max_fee_per_gas, Some(30000000000));
796        assert_eq!(params.max_priority_fee_per_gas, Some(2000000000));
797    }
798
799    #[tokio::test]
800    async fn test_speed_legacy_based_transaction() {
801        let mut provider = MockEvmProviderTrait::new();
802        provider
803            .expect_get_balance()
804            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
805        provider
806            .expect_get_gas_price()
807            .returning(|| async { Ok(20000000000) }.boxed());
808
809        let relayer = create_mock_relayer();
810        let gas_price_service = EvmGasPriceService::new(provider, create_mock_evm_network("celo"));
811
812        let tx_data = EvmTransactionData {
813            gas_price: None,
814            speed: Some(Speed::Fast),
815            ..Default::default()
816        };
817
818        let mut provider = MockEvmProviderTrait::new();
819        provider
820            .expect_get_balance()
821            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
822        provider
823            .expect_get_gas_price()
824            .returning(|| async { Ok(20000000000) }.boxed());
825
826        let pc = PriceCalculator::new(gas_price_service, NetworkExtraFeeCalculator::None);
827
828        let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
829        assert!(result.is_ok());
830        let params = result.unwrap();
831        assert!(
832            params.gas_price.is_some()
833                || (params.max_fee_per_gas.is_some() && params.max_priority_fee_per_gas.is_some())
834        );
835    }
836
837    #[tokio::test]
838    async fn test_invalid_transaction_type() {
839        let mut provider = MockEvmProviderTrait::new();
840        provider
841            .expect_get_balance()
842            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
843
844        let relayer = create_mock_relayer();
845        let gas_price_service =
846            EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"));
847
848        let tx_data = EvmTransactionData {
849            gas_price: None,
850            ..Default::default()
851        };
852
853        let mut provider = MockEvmProviderTrait::new();
854        provider
855            .expect_get_balance()
856            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
857
858        let pc = PriceCalculator::new(gas_price_service, NetworkExtraFeeCalculator::None);
859
860        let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
861        assert!(result.is_err());
862        assert!(matches!(
863            result.unwrap_err(),
864            TransactionError::NotSupported(_)
865        ));
866    }
867
868    #[tokio::test]
869    async fn test_gas_price_cap() {
870        let mut provider = MockEvmProviderTrait::new();
871        provider
872            .expect_get_balance()
873            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
874
875        let mut relayer = create_mock_relayer();
876        let gas_price_service =
877            EvmGasPriceService::new(provider, create_mock_evm_network("mainnet"));
878
879        // Update policies with new EVM policy
880        let evm_policy = RelayerEvmPolicy {
881            gas_price_cap: Some(10000000000),
882            eip1559_pricing: Some(true),
883            ..RelayerEvmPolicy::default()
884        };
885        relayer.policies = RelayerNetworkPolicy::Evm(evm_policy);
886
887        let tx_data = EvmTransactionData {
888            gas_price: Some(20000000000), // Higher than cap
889            ..Default::default()
890        };
891
892        let mut provider = MockEvmProviderTrait::new();
893        provider
894            .expect_get_balance()
895            .returning(|_| async { Ok(U256::from(1000000000000000000u128)) }.boxed());
896
897        let pc = PriceCalculator::new(gas_price_service, NetworkExtraFeeCalculator::None);
898
899        let result = pc.get_transaction_price_params(&tx_data, &relayer).await;
900        assert!(result.is_ok());
901        let params = result.unwrap();
902        assert_eq!(params.gas_price, Some(10000000000)); // Should be capped
903    }
904
905    #[test]
906    fn test_get_base_fee_multiplier() {
907        let mainnet = create_mock_evm_network("mainnet");
908        let multiplier = super::get_base_fee_multiplier(&mainnet);
909        // 90s with ~12s blocks = ~7.5 blocks => ~2.4 multiplier
910        assert!(multiplier > 2_300_000_000 && multiplier < 2_500_000_000);
911
912        let optimism = create_mock_evm_network("optimism");
913        let multiplier = super::get_base_fee_multiplier(&optimism);
914        // 2s block time => ~45 blocks => capped at 10.0
915        assert_eq!(multiplier, MAX_BASE_FEE_MULTIPLIER);
916    }
917
918    #[test]
919    fn test_calculate_max_fee_per_gas() {
920        let network = create_mock_evm_network("mainnet");
921        let base_fee = 100_000_000_000u128; // 100 Gwei
922        let priority_fee = 2_000_000_000u128; // 2 Gwei
923
924        let max_fee = super::calculate_max_fee_per_gas(base_fee, priority_fee, &network);
925        println!("max_fee: {:?}", max_fee);
926        // With mainnet's multiplier (~2.4):
927        // base_fee * multiplier + priority_fee ≈ 100 * 2.4 + 2 ≈ 242 Gwei
928        assert!(max_fee > 240_000_000_000 && max_fee < 245_000_000_000);
929    }
930
931    #[tokio::test]
932    async fn test_handle_eip1559_speed() {
933        let mut mock_gas_price_service = MockEvmGasPriceServiceTrait::new();
934
935        // Mock the gas price service's get_prices_from_json_rpc method
936        let test_data = [
937            (Speed::SafeLow, 1_000_000_000),
938            (Speed::Average, 2_000_000_000),
939            (Speed::Fast, 3_000_000_000),
940            (Speed::Fastest, 4_000_000_000),
941        ];
942        // Create mock prices
943        let mock_prices = GasPrices {
944            legacy_prices: SpeedPrices {
945                safe_low: 10_000_000_000,
946                average: 12_500_000_000,
947                fast: 15_000_000_000,
948                fastest: 20_000_000_000,
949            },
950            max_priority_fee_per_gas: SpeedPrices {
951                safe_low: 1_000_000_000,
952                average: 2_000_000_000,
953                fast: 3_000_000_000,
954                fastest: 4_000_000_000,
955            },
956            base_fee_per_gas: 50_000_000_000,
957        };
958
959        // Mock get_prices_from_json_rpc
960        mock_gas_price_service
961            .expect_get_prices_from_json_rpc()
962            .returning(move || {
963                let prices = mock_prices.clone();
964                Box::pin(async move { Ok(prices) })
965            });
966
967        // Mock the network method
968        let network = create_mock_evm_network("mainnet");
969        mock_gas_price_service
970            .expect_network()
971            .return_const(network);
972
973        // Construct our PriceCalculator with the mocked gas service
974        let pc = PriceCalculator::new(mock_gas_price_service, NetworkExtraFeeCalculator::None);
975
976        for (speed, expected_priority_fee) in test_data {
977            // Call our internal fetch_eip1559_speed_params, which replaced handle_eip1559_speed
978            let result = pc.fetch_eip1559_speed_params(&speed).await;
979            assert!(result.is_ok());
980            let params = result.unwrap();
981            // Verify max_priority_fee matches expected value
982            assert_eq!(params.max_priority_fee_per_gas, Some(expected_priority_fee));
983
984            // Verify max_fee calculation
985            // max_fee = base_fee * multiplier + priority_fee
986            // ≈ (50 * 2.4 + priority_fee_in_gwei) Gwei
987            let max_fee = params.max_fee_per_gas.unwrap();
988            let expected_base_portion = 120_000_000_000; // ~50 Gwei * 2.4
989            assert!(max_fee < expected_base_portion + expected_priority_fee + 2_000_000_000);
990        }
991    }
992
993    #[tokio::test]
994    async fn test_calculate_bumped_gas_price_eip1559_basic() {
995        let mut mock_service = MockEvmGasPriceServiceTrait::new();
996        let mock_prices = GasPrices {
997            legacy_prices: SpeedPrices {
998                safe_low: 8_000_000_000,
999                average: 10_000_000_000,
1000                fast: 12_000_000_000,
1001                fastest: 15_000_000_000,
1002            },
1003            max_priority_fee_per_gas: SpeedPrices {
1004                safe_low: 1_000_000_000,
1005                average: 2_000_000_000,
1006                fast: 3_000_000_000,
1007                fastest: 4_000_000_000,
1008            },
1009            base_fee_per_gas: 50_000_000_000,
1010        };
1011        mock_service
1012            .expect_get_prices_from_json_rpc()
1013            .returning(move || {
1014                let prices = mock_prices.clone();
1015                Box::pin(async move { Ok(prices) })
1016            });
1017        mock_service
1018            .expect_network()
1019            .return_const(create_mock_evm_network("mainnet"));
1020
1021        let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1022        let mut relayer = create_mock_relayer();
1023        // Example cap to demonstrate bump capping
1024        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1025            gas_price_cap: Some(300_000_000_000u128),
1026            ..Default::default()
1027        });
1028
1029        let tx_data = EvmTransactionData {
1030            max_fee_per_gas: Some(100_000_000_000),
1031            max_priority_fee_per_gas: Some(2_000_000_000),
1032            speed: Some(Speed::Fast),
1033            ..Default::default()
1034        };
1035
1036        let bumped = pc
1037            .calculate_bumped_gas_price(&tx_data, &relayer)
1038            .await
1039            .unwrap();
1040        assert!(bumped.max_fee_per_gas.unwrap() >= 110_000_000_000); // >= 10% bump
1041        assert!(bumped.max_priority_fee_per_gas.unwrap() >= 2_200_000_000); // >= 10% bump
1042    }
1043
1044    #[tokio::test]
1045    async fn test_calculate_bumped_gas_price_eip1559_market_lower_than_min_bump() {
1046        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1047        let mock_prices = GasPrices {
1048            legacy_prices: SpeedPrices::default(),
1049            max_priority_fee_per_gas: SpeedPrices {
1050                safe_low: 1_500_000_000, // market priority
1051                average: 2_500_000_000,
1052                fast: 2_700_000_000,
1053                fastest: 3_000_000_000,
1054            },
1055            base_fee_per_gas: 30_000_000_000,
1056        };
1057        mock_service
1058            .expect_get_prices_from_json_rpc()
1059            .returning(move || {
1060                let prices = mock_prices.clone();
1061                Box::pin(async move { Ok(prices) })
1062            });
1063        mock_service
1064            .expect_network()
1065            .return_const(create_mock_evm_network("mainnet"));
1066
1067        let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1068        let relayer = create_mock_relayer();
1069
1070        // Old max_priority_fee: 2.0 Gwei, new market is 1.5 Gwei (less)
1071        // Should use min bump (2.2 Gwei) instead
1072        let tx_data = EvmTransactionData {
1073            max_fee_per_gas: Some(20_000_000_000),
1074            max_priority_fee_per_gas: Some(2_000_000_000),
1075            speed: Some(Speed::SafeLow),
1076            ..Default::default()
1077        };
1078
1079        let bumped = pc
1080            .calculate_bumped_gas_price(&tx_data, &relayer)
1081            .await
1082            .unwrap();
1083        assert!(bumped.max_priority_fee_per_gas.unwrap() >= 2_200_000_000);
1084        assert!(bumped.max_fee_per_gas.unwrap() > 20_000_000_000);
1085    }
1086
1087    #[tokio::test]
1088    async fn test_calculate_bumped_gas_price_legacy_basic() {
1089        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1090        let mock_prices = GasPrices {
1091            legacy_prices: SpeedPrices {
1092                safe_low: 10_000_000_000,
1093                average: 12_000_000_000,
1094                fast: 14_000_000_000,
1095                fastest: 18_000_000_000,
1096            },
1097            max_priority_fee_per_gas: SpeedPrices::default(),
1098            base_fee_per_gas: 0,
1099        };
1100        mock_service
1101            .expect_get_prices_from_json_rpc()
1102            .returning(move || {
1103                let prices = mock_prices.clone();
1104                Box::pin(async move { Ok(prices) })
1105            });
1106        mock_service
1107            .expect_network()
1108            .return_const(create_mock_evm_network("mainnet"));
1109
1110        let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1111        let relayer = create_mock_relayer();
1112        let tx_data = EvmTransactionData {
1113            gas_price: Some(10_000_000_000),
1114            speed: Some(Speed::Fast),
1115            ..Default::default()
1116        };
1117
1118        let bumped = pc
1119            .calculate_bumped_gas_price(&tx_data, &relayer)
1120            .await
1121            .unwrap();
1122        assert!(bumped.gas_price.unwrap() >= 11_000_000_000); // at least 10% bump
1123    }
1124
1125    #[tokio::test]
1126    async fn test_calculate_bumped_gas_price_missing_params() {
1127        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1128
1129        // Add the missing expectation for get_prices_from_json_rpc
1130        mock_service
1131            .expect_get_prices_from_json_rpc()
1132            .times(1)
1133            .returning(|| Box::pin(async { Ok(GasPrices::default()) }));
1134
1135        let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1136        let relayer = create_mock_relayer();
1137        // Both max_fee_per_gas, max_priority_fee_per_gas, and gas_price absent
1138        let tx_data = EvmTransactionData {
1139            gas_price: None,
1140            max_fee_per_gas: None,
1141            max_priority_fee_per_gas: None,
1142            ..Default::default()
1143        };
1144
1145        let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1146        assert!(result.is_err());
1147        if let Err(TransactionError::InvalidType(msg)) = result {
1148            assert!(msg.contains("missing required gas price parameters"));
1149        } else {
1150            panic!("Expected InvalidType error");
1151        }
1152    }
1153
1154    #[tokio::test]
1155    async fn test_calculate_bumped_gas_price_capped() {
1156        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1157        let mock_prices = GasPrices {
1158            legacy_prices: SpeedPrices::default(),
1159            max_priority_fee_per_gas: SpeedPrices {
1160                safe_low: 4_000_000_000,
1161                average: 5_000_000_000,
1162                fast: 6_000_000_000,
1163                fastest: 8_000_000_000,
1164            },
1165            base_fee_per_gas: 100_000_000_000,
1166        };
1167        mock_service
1168            .expect_get_prices_from_json_rpc()
1169            .returning(move || {
1170                let prices = mock_prices.clone();
1171                Box::pin(async move { Ok(prices) })
1172            });
1173        mock_service
1174            .expect_network()
1175            .return_const(create_mock_evm_network("mainnet"));
1176
1177        let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1178        let mut relayer = create_mock_relayer();
1179        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1180            gas_price_cap: Some(105_000_000_000),
1181            ..Default::default()
1182        });
1183
1184        let tx_data = EvmTransactionData {
1185            max_fee_per_gas: Some(90_000_000_000),
1186            max_priority_fee_per_gas: Some(4_000_000_000),
1187            speed: Some(Speed::Fastest),
1188            ..Default::default()
1189        };
1190
1191        // Normally, we'd expect ~ (100 Gwei * 2.4) + 8 Gwei > 248 Gwei. We'll cap it at 105 Gwei.
1192        let bumped = pc
1193            .calculate_bumped_gas_price(&tx_data, &relayer)
1194            .await
1195            .unwrap();
1196        assert!(bumped.max_fee_per_gas.unwrap() <= 105_000_000_000);
1197        assert!(bumped.max_priority_fee_per_gas.unwrap() <= 105_000_000_000);
1198    }
1199
1200    #[tokio::test]
1201    async fn test_is_min_bumped_flag_eip1559() {
1202        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1203        let mock_prices = GasPrices {
1204            legacy_prices: SpeedPrices::default(),
1205            max_priority_fee_per_gas: SpeedPrices {
1206                safe_low: 1_000_000_000,
1207                average: 2_000_000_000,
1208                fast: 3_000_000_000,
1209                fastest: 4_000_000_000,
1210            },
1211            base_fee_per_gas: 40_000_000_000,
1212        };
1213        mock_service
1214            .expect_get_prices_from_json_rpc()
1215            .returning(move || {
1216                let prices = mock_prices.clone();
1217                Box::pin(async move { Ok(prices) })
1218            });
1219        mock_service
1220            .expect_network()
1221            .return_const(create_mock_evm_network("mainnet"));
1222
1223        let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1224        let mut relayer = create_mock_relayer();
1225
1226        // Case 1: Price high enough - should result in is_min_bumped = true
1227        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1228            gas_price_cap: Some(200_000_000_000u128),
1229            ..Default::default()
1230        });
1231
1232        let tx_data = EvmTransactionData {
1233            max_fee_per_gas: Some(50_000_000_000),
1234            max_priority_fee_per_gas: Some(2_000_000_000),
1235            speed: Some(Speed::Fast),
1236            ..Default::default()
1237        };
1238
1239        let bumped = pc
1240            .calculate_bumped_gas_price(&tx_data, &relayer)
1241            .await
1242            .unwrap();
1243        assert_eq!(
1244            bumped.is_min_bumped,
1245            Some(true),
1246            "Should be min bumped when prices are high enough"
1247        );
1248
1249        // Case 2: Gas price cap too low - should result in is_min_bumped = false
1250        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1251            gas_price_cap: Some(50_000_000_000u128), // Cap is below the min bump for max_fee_per_gas
1252            ..Default::default()
1253        });
1254
1255        let tx_data = EvmTransactionData {
1256            max_fee_per_gas: Some(50_000_000_000),
1257            max_priority_fee_per_gas: Some(2_000_000_000),
1258            speed: Some(Speed::Fast),
1259            ..Default::default()
1260        };
1261
1262        let bumped = pc
1263            .calculate_bumped_gas_price(&tx_data, &relayer)
1264            .await
1265            .unwrap();
1266        // Since min bump is 10%, original was 50 Gwei, min is 55 Gwei, but cap is 50 Gwei
1267        assert_eq!(
1268            bumped.is_min_bumped,
1269            Some(false),
1270            "Should not be min bumped when cap is too low"
1271        );
1272    }
1273
1274    #[tokio::test]
1275    async fn test_is_min_bumped_flag_legacy() {
1276        let mut mock_service = MockEvmGasPriceServiceTrait::new();
1277        let mock_prices = GasPrices {
1278            legacy_prices: SpeedPrices {
1279                safe_low: 8_000_000_000,
1280                average: 10_000_000_000,
1281                fast: 12_000_000_000,
1282                fastest: 15_000_000_000,
1283            },
1284            max_priority_fee_per_gas: SpeedPrices::default(),
1285            base_fee_per_gas: 0,
1286        };
1287        mock_service
1288            .expect_get_prices_from_json_rpc()
1289            .returning(move || {
1290                let prices = mock_prices.clone();
1291                Box::pin(async move { Ok(prices) })
1292            });
1293        mock_service
1294            .expect_network()
1295            .return_const(create_mock_evm_network("mainnet"));
1296
1297        let pc = PriceCalculator::new(mock_service, NetworkExtraFeeCalculator::None);
1298        let mut relayer = create_mock_relayer();
1299
1300        // Case 1: Regular case, cap is high enough
1301        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1302            gas_price_cap: Some(100_000_000_000u128),
1303            ..Default::default()
1304        });
1305
1306        let tx_data = EvmTransactionData {
1307            gas_price: Some(10_000_000_000),
1308            speed: Some(Speed::Fast),
1309            ..Default::default()
1310        };
1311
1312        let bumped = pc
1313            .calculate_bumped_gas_price(&tx_data, &relayer)
1314            .await
1315            .unwrap();
1316        assert_eq!(
1317            bumped.is_min_bumped,
1318            Some(true),
1319            "Should be min bumped with sufficient cap"
1320        );
1321
1322        // Case 2: Cap too low
1323        relayer.policies = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1324            gas_price_cap: Some(10_000_000_000u128), // Same as original, preventing the 10% bump
1325            ..Default::default()
1326        });
1327
1328        let bumped = pc
1329            .calculate_bumped_gas_price(&tx_data, &relayer)
1330            .await
1331            .unwrap();
1332        assert_eq!(
1333            bumped.is_min_bumped,
1334            Some(false),
1335            "Should not be min bumped with insufficient cap"
1336        );
1337    }
1338
1339    #[tokio::test]
1340    async fn test_calculate_bumped_gas_price_with_extra_fee() {
1341        // Set up mock gas price service
1342        let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
1343        mock_gas_service
1344            .expect_get_prices_from_json_rpc()
1345            .returning(|| {
1346                Box::pin(async {
1347                    Ok(GasPrices {
1348                        legacy_prices: SpeedPrices {
1349                            safe_low: 10_000_000_000,
1350                            average: 12_000_000_000,
1351                            fast: 14_000_000_000,
1352                            fastest: 18_000_000_000,
1353                        },
1354                        max_priority_fee_per_gas: SpeedPrices::default(),
1355                        base_fee_per_gas: 40_000_000_000,
1356                    })
1357                })
1358            });
1359        mock_gas_service
1360            .expect_network()
1361            .return_const(create_mock_evm_network("mainnet"));
1362
1363        // Create a PriceCalculator without extra fee service first
1364        let pc = PriceCalculator::new(mock_gas_service, NetworkExtraFeeCalculator::None);
1365
1366        // Create test transaction and relayer
1367        let relayer = create_mock_relayer();
1368        let tx_data = EvmTransactionData {
1369            max_fee_per_gas: Some(50_000_000_000),
1370            max_priority_fee_per_gas: Some(2_000_000_000),
1371            speed: Some(Speed::Fast),
1372            ..Default::default()
1373        };
1374
1375        // Call the method under test
1376        let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1377
1378        // Verify extra fee is None when no extra fee service is used
1379        assert!(result.is_ok());
1380        let price_params = result.unwrap();
1381        assert_eq!(price_params.extra_fee, None);
1382    }
1383
1384    #[tokio::test]
1385    async fn test_calculate_bumped_gas_price_with_mock_extra_fee() {
1386        // Set up mock gas price service
1387        let mut mock_gas_service = MockEvmGasPriceServiceTrait::new();
1388        mock_gas_service
1389            .expect_get_prices_from_json_rpc()
1390            .returning(|| {
1391                Box::pin(async {
1392                    Ok(GasPrices {
1393                        legacy_prices: SpeedPrices {
1394                            safe_low: 10_000_000_000,
1395                            average: 12_000_000_000,
1396                            fast: 14_000_000_000,
1397                            fastest: 18_000_000_000,
1398                        },
1399                        max_priority_fee_per_gas: SpeedPrices::default(),
1400                        base_fee_per_gas: 40_000_000_000,
1401                    })
1402                })
1403            });
1404        mock_gas_service
1405            .expect_network()
1406            .return_const(create_mock_evm_network("mainnet"));
1407
1408        // Create mock extra fee service
1409        let mut mock_extra_fee_service = MockNetworkExtraFeeCalculatorServiceTrait::new();
1410
1411        // Set up expected return value for the extra fee
1412        let expected_extra_fee = U256::from(123456789u64);
1413        mock_extra_fee_service
1414            .expect_get_extra_fee()
1415            .returning(move |_| Box::pin(async move { Ok(expected_extra_fee) }));
1416
1417        // Use the Mock variant from NetworkExtraFeeCalculator enum
1418        let extra_fee_calculator = NetworkExtraFeeCalculator::Mock(mock_extra_fee_service);
1419
1420        // Create the price calculator with the mock extra fee service
1421        let pc = PriceCalculator::new(mock_gas_service, extra_fee_calculator);
1422
1423        // Create test transaction and relayer
1424        let relayer = create_mock_relayer();
1425        let tx_data = EvmTransactionData {
1426            max_fee_per_gas: Some(50_000_000_000),
1427            max_priority_fee_per_gas: Some(2_000_000_000),
1428            speed: Some(Speed::Fast),
1429            ..Default::default()
1430        };
1431
1432        // Call the method under test
1433        let result = pc.calculate_bumped_gas_price(&tx_data, &relayer).await;
1434
1435        // Verify extra fee was properly included
1436        assert!(result.is_ok());
1437        let price_params = result.unwrap();
1438        assert_eq!(
1439            price_params.extra_fee,
1440            Some(expected_extra_fee.try_into().unwrap_or(0))
1441        );
1442    }
1443
1444    #[test]
1445    fn test_calculate_total_cost_eip1559() {
1446        let price_params = PriceParams {
1447            gas_price: None,
1448            max_fee_per_gas: Some(30_000_000_000),
1449            max_priority_fee_per_gas: Some(2_000_000_000),
1450            is_min_bumped: None,
1451            extra_fee: None,
1452            total_cost: U256::ZERO,
1453        };
1454
1455        let gas_limit = 100_000;
1456        let value = U256::from(1_000_000_000_000_000_000u128); // 1 ETH
1457        let is_eip1559 = true;
1458
1459        let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1460
1461        // Expected: max_fee_per_gas * gas_limit + value
1462        let expected = U256::from(30_000_000_000u128) * U256::from(gas_limit) + value;
1463        assert_eq!(total_cost, expected);
1464    }
1465
1466    #[test]
1467    fn test_calculate_total_cost_legacy() {
1468        let price_params = PriceParams {
1469            gas_price: Some(20_000_000_000),
1470            max_fee_per_gas: None,
1471            max_priority_fee_per_gas: None,
1472            is_min_bumped: None,
1473            extra_fee: None,
1474            total_cost: U256::ZERO,
1475        };
1476
1477        let gas_limit = 100_000;
1478        let value = U256::from(1_000_000_000_000_000_000u128); // 1 ETH
1479        let is_eip1559 = false;
1480
1481        let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1482
1483        // Expected: gas_price * gas_limit + value
1484        let expected = U256::from(20_000_000_000u128) * U256::from(gas_limit) + value;
1485        assert_eq!(total_cost, expected);
1486    }
1487
1488    #[test]
1489    fn test_calculate_total_cost_with_extra_fee() {
1490        let price_params = PriceParams {
1491            gas_price: Some(20_000_000_000),
1492            max_fee_per_gas: None,
1493            max_priority_fee_per_gas: None,
1494            is_min_bumped: None,
1495            extra_fee: Some(5_000_000_000),
1496            total_cost: U256::ZERO,
1497        };
1498
1499        let gas_limit = 100_000;
1500        let value = U256::from(1_000_000_000_000_000_000u128); // 1 ETH
1501        let is_eip1559 = false;
1502
1503        let total_cost = price_params.calculate_total_cost(is_eip1559, gas_limit, value);
1504
1505        let expected = U256::from(20_000_000_000u128) * U256::from(gas_limit)
1506            + value
1507            + U256::from(5_000_000_000u128);
1508        assert_eq!(total_cost, expected);
1509    }
1510
1511    #[test]
1512    fn test_calculate_total_cost_zero_values() {
1513        let price_params = PriceParams {
1514            gas_price: Some(0),
1515            max_fee_per_gas: Some(0),
1516            max_priority_fee_per_gas: Some(0),
1517            is_min_bumped: None,
1518            extra_fee: Some(0),
1519            total_cost: U256::ZERO,
1520        };
1521
1522        let gas_limit = 0;
1523        let value = U256::from(0);
1524
1525        let legacy_total_cost = price_params.calculate_total_cost(false, gas_limit, value);
1526        assert_eq!(legacy_total_cost, U256::ZERO);
1527
1528        let eip1559_total_cost = price_params.calculate_total_cost(true, gas_limit, value);
1529        assert_eq!(eip1559_total_cost, U256::ZERO);
1530    }
1531
1532    #[test]
1533    fn test_calculate_total_cost_missing_values() {
1534        let price_params = PriceParams {
1535            gas_price: None,
1536            max_fee_per_gas: None,
1537            max_priority_fee_per_gas: None,
1538            is_min_bumped: None,
1539            extra_fee: None,
1540            total_cost: U256::ZERO,
1541        };
1542
1543        let gas_limit = 100_000;
1544        let value = U256::from(1_000_000_000_000_000_000u128);
1545
1546        let legacy_total = price_params.calculate_total_cost(false, gas_limit, value);
1547        assert_eq!(legacy_total, value);
1548
1549        let eip1559_total = price_params.calculate_total_cost(true, gas_limit, value);
1550        assert_eq!(eip1559_total, value);
1551    }
1552
1553    #[test]
1554    fn test_calculate_min_bump_normal_cases() {
1555        let base_price = 20_000_000_000u128; // 20 Gwei
1556        let expected = 22_000_000_000u128; // 22 Gwei (10% bump)
1557        assert_eq!(calculate_min_bump(base_price), expected);
1558
1559        let base_price = 1_000_000_000u128;
1560        let expected = 1_100_000_000u128; // 1.1 Gwei
1561        assert_eq!(calculate_min_bump(base_price), expected);
1562
1563        let base_price = 100_000_000_000u128;
1564        let expected = 110_000_000_000u128; // 110 Gwei
1565        assert_eq!(calculate_min_bump(base_price), expected);
1566    }
1567
1568    #[test]
1569    fn test_calculate_min_bump_edge_cases() {
1570        // Test with zero - should return 1 wei (minimum bump)
1571        assert_eq!(calculate_min_bump(0), 1);
1572
1573        // Test with 1 wei - should return at least 2 wei
1574        let result = calculate_min_bump(1);
1575        assert!(result >= 2);
1576
1577        // Test with very small values where 10% bump rounds down to 0
1578        let base_price = 5u128; // 5 wei
1579        let result = calculate_min_bump(base_price);
1580        assert!(
1581            result > base_price,
1582            "Result {} should be greater than base_price {}",
1583            result,
1584            base_price
1585        );
1586
1587        let base_price = 9u128;
1588        let result = calculate_min_bump(base_price);
1589        assert_eq!(
1590            result, 10u128,
1591            "9 wei should bump to 10 wei (minimum 1 wei increase)"
1592        );
1593    }
1594
1595    #[test]
1596    fn test_calculate_min_bump_large_values() {
1597        // Test with large values to ensure no overflow
1598        let base_price = u128::MAX / 2;
1599        let result = calculate_min_bump(base_price);
1600        assert!(result > base_price);
1601
1602        // Test near maximum value
1603        let base_price = u128::MAX - 1000;
1604        let result = calculate_min_bump(base_price);
1605        // Should not panic and should return a reasonable value
1606        assert!(result >= base_price.saturating_add(1));
1607    }
1608
1609    #[test]
1610    fn test_calculate_min_bump_overflow_protection() {
1611        let base_price = u128::MAX;
1612        let result = calculate_min_bump(base_price);
1613        assert_eq!(result, u128::MAX);
1614
1615        let base_price = (u128::MAX / 11) * 10 + 1;
1616        let result = calculate_min_bump(base_price);
1617        assert!(result >= base_price);
1618    }
1619
1620    #[test]
1621    fn test_calculate_min_bump_minimum_increase_guarantee() {
1622        // Test that the function always increases by at least 1 wei
1623        let test_cases = vec![0, 1, 2, 5, 9, 10, 100, 1000, 10000];
1624
1625        for base_price in test_cases {
1626            let result = calculate_min_bump(base_price);
1627            assert!(
1628                result > base_price,
1629                "calculate_min_bump({}) = {} should be greater than base_price",
1630                base_price,
1631                result
1632            );
1633            assert!(
1634                result >= base_price.saturating_add(1),
1635                "calculate_min_bump({}) = {} should be at least base_price + 1",
1636                base_price,
1637                result
1638            );
1639        }
1640    }
1641}