openzeppelin_relayer/services/gas/fetchers/
default.rs

1//! Default gas price fetcher using standard EVM methods.
2//!
3//! This module provides the fallback gas price fetcher strategy that works
4//! with any EVM-compatible network using the standard `eth_gasPrice` RPC method.
5
6use crate::{
7    models::EvmNetwork,
8    services::provider::{evm::EvmProviderTrait, ProviderError},
9};
10
11/// Universal gas price fetcher using standard EVM RPC methods.
12#[derive(Debug, Clone)]
13pub struct DefaultGasPriceFetcher;
14
15impl DefaultGasPriceFetcher {
16    /// Fetches gas price using `eth_gasPrice`.
17    pub async fn fetch_gas_price<P: EvmProviderTrait>(
18        &self,
19        provider: &P,
20        _network: &EvmNetwork,
21    ) -> Result<Option<u128>, ProviderError> {
22        match provider.get_gas_price().await {
23            Ok(gas_price) => Ok(Some(gas_price)),
24            Err(e) => Err(e),
25        }
26    }
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32    use crate::services::provider::evm::MockEvmProviderTrait;
33    use futures::FutureExt;
34
35    #[tokio::test]
36    async fn test_default_fetcher_success() {
37        let mut mock_provider = MockEvmProviderTrait::new();
38        mock_provider
39            .expect_get_gas_price()
40            .times(1)
41            .returning(|| async { Ok(20_000_000_000u128) }.boxed());
42
43        let fetcher = DefaultGasPriceFetcher;
44        let network = EvmNetwork {
45            network: "ethereum".to_string(),
46            rpc_urls: vec!["https://mainnet.infura.io".to_string()],
47            explorer_urls: None,
48            average_blocktime_ms: 12000,
49            is_testnet: false,
50            tags: vec![],
51            chain_id: 1,
52            required_confirmations: 12,
53            features: vec!["eip1559".to_string()],
54            symbol: "ETH".to_string(),
55            gas_price_cache: None,
56        };
57
58        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
59        assert!(result.is_ok());
60        assert_eq!(result.unwrap(), Some(20_000_000_000u128));
61    }
62
63    #[tokio::test]
64    async fn test_default_estimator_failure() {
65        let mut mock_provider = MockEvmProviderTrait::new();
66        mock_provider
67            .expect_get_gas_price()
68            .times(1)
69            .returning(|| async { Err(ProviderError::Timeout) }.boxed());
70
71        let fetcher = DefaultGasPriceFetcher;
72        let network = EvmNetwork {
73            network: "ethereum".to_string(),
74            rpc_urls: vec!["https://mainnet.infura.io".to_string()],
75            explorer_urls: None,
76            average_blocktime_ms: 12000,
77            is_testnet: false,
78            tags: vec![],
79            chain_id: 1,
80            required_confirmations: 12,
81            features: vec!["eip1559".to_string()],
82            symbol: "ETH".to_string(),
83            gas_price_cache: None,
84        };
85
86        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
87        assert!(result.is_err());
88        assert!(matches!(result.unwrap_err(), ProviderError::Timeout));
89    }
90
91    #[tokio::test]
92    async fn test_default_estimator_network_error() {
93        let mut mock_provider = MockEvmProviderTrait::new();
94        mock_provider.expect_get_gas_price().times(1).returning(|| {
95            async { Err(ProviderError::Other("Connection refused".to_string())) }.boxed()
96        });
97
98        let fetcher = DefaultGasPriceFetcher;
99        let network = EvmNetwork {
100            network: "polygon".to_string(),
101            rpc_urls: vec!["https://polygon-rpc.com".to_string()],
102            explorer_urls: None,
103            average_blocktime_ms: 2000,
104            is_testnet: false,
105            tags: vec![],
106            chain_id: 137,
107            required_confirmations: 10,
108            features: vec!["eip1559".to_string()],
109            symbol: "MATIC".to_string(),
110            gas_price_cache: None,
111        };
112
113        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
114        assert!(result.is_err());
115        assert!(matches!(result.unwrap_err(), ProviderError::Other(_)));
116    }
117
118    #[tokio::test]
119    async fn test_default_estimator_ethereum() {
120        let mut mock_provider = MockEvmProviderTrait::new();
121        mock_provider
122            .expect_get_gas_price()
123            .times(1)
124            .returning(|| async { Ok(1_000_000_000u128) }.boxed());
125
126        let fetcher = DefaultGasPriceFetcher;
127        let network = EvmNetwork {
128            network: "ethereum".to_string(),
129            rpc_urls: vec!["https://mainnet.infura.io".to_string()],
130            explorer_urls: None,
131            average_blocktime_ms: 12000,
132            is_testnet: false,
133            tags: vec![],
134            chain_id: 1,
135            required_confirmations: 12,
136            features: vec!["eip1559".to_string()],
137            symbol: "ETH".to_string(),
138            gas_price_cache: None,
139        };
140
141        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
142        assert!(result.is_ok());
143        assert_eq!(result.unwrap(), Some(1_000_000_000u128));
144    }
145
146    #[tokio::test]
147    async fn test_default_estimator_polygon() {
148        let mut mock_provider = MockEvmProviderTrait::new();
149        mock_provider
150            .expect_get_gas_price()
151            .times(1)
152            .returning(|| async { Ok(137_000_000_000u128) }.boxed());
153
154        let fetcher = DefaultGasPriceFetcher;
155        let network = EvmNetwork {
156            network: "polygon".to_string(),
157            rpc_urls: vec!["https://polygon-rpc.com".to_string()],
158            explorer_urls: None,
159            average_blocktime_ms: 2000,
160            is_testnet: false,
161            tags: vec![],
162            chain_id: 137,
163            required_confirmations: 10,
164            features: vec!["eip1559".to_string()],
165            symbol: "MATIC".to_string(),
166            gas_price_cache: None,
167        };
168
169        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
170        assert!(result.is_ok());
171        assert_eq!(result.unwrap(), Some(137_000_000_000u128));
172    }
173}