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![crate::models::RpcConfig::new(
47                "https://mainnet.infura.io".to_string(),
48            )],
49            explorer_urls: None,
50            average_blocktime_ms: 12000,
51            is_testnet: false,
52            tags: vec![],
53            chain_id: 1,
54            required_confirmations: 12,
55            features: vec!["eip1559".to_string()],
56            symbol: "ETH".to_string(),
57            gas_price_cache: None,
58        };
59
60        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
61        assert!(result.is_ok());
62        assert_eq!(result.unwrap(), Some(20_000_000_000u128));
63    }
64
65    #[tokio::test]
66    async fn test_default_estimator_failure() {
67        let mut mock_provider = MockEvmProviderTrait::new();
68        mock_provider
69            .expect_get_gas_price()
70            .times(1)
71            .returning(|| async { Err(ProviderError::Timeout) }.boxed());
72
73        let fetcher = DefaultGasPriceFetcher;
74        let network = EvmNetwork {
75            network: "ethereum".to_string(),
76            rpc_urls: vec![crate::models::RpcConfig::new(
77                "https://mainnet.infura.io".to_string(),
78            )],
79            explorer_urls: None,
80            average_blocktime_ms: 12000,
81            is_testnet: false,
82            tags: vec![],
83            chain_id: 1,
84            required_confirmations: 12,
85            features: vec!["eip1559".to_string()],
86            symbol: "ETH".to_string(),
87            gas_price_cache: None,
88        };
89
90        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
91        assert!(result.is_err());
92        assert!(matches!(result.unwrap_err(), ProviderError::Timeout));
93    }
94
95    #[tokio::test]
96    async fn test_default_estimator_network_error() {
97        let mut mock_provider = MockEvmProviderTrait::new();
98        mock_provider.expect_get_gas_price().times(1).returning(|| {
99            async { Err(ProviderError::Other("Connection refused".to_string())) }.boxed()
100        });
101
102        let fetcher = DefaultGasPriceFetcher;
103        let network = EvmNetwork {
104            network: "polygon".to_string(),
105            rpc_urls: vec![crate::models::RpcConfig::new(
106                "https://polygon-rpc.com".to_string(),
107            )],
108            explorer_urls: None,
109            average_blocktime_ms: 2000,
110            is_testnet: false,
111            tags: vec![],
112            chain_id: 137,
113            required_confirmations: 10,
114            features: vec!["eip1559".to_string()],
115            symbol: "MATIC".to_string(),
116            gas_price_cache: None,
117        };
118
119        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
120        assert!(result.is_err());
121        assert!(matches!(result.unwrap_err(), ProviderError::Other(_)));
122    }
123
124    #[tokio::test]
125    async fn test_default_estimator_ethereum() {
126        let mut mock_provider = MockEvmProviderTrait::new();
127        mock_provider
128            .expect_get_gas_price()
129            .times(1)
130            .returning(|| async { Ok(1_000_000_000u128) }.boxed());
131
132        let fetcher = DefaultGasPriceFetcher;
133        let network = EvmNetwork {
134            network: "ethereum".to_string(),
135            rpc_urls: vec![crate::models::RpcConfig::new(
136                "https://mainnet.infura.io".to_string(),
137            )],
138            explorer_urls: None,
139            average_blocktime_ms: 12000,
140            is_testnet: false,
141            tags: vec![],
142            chain_id: 1,
143            required_confirmations: 12,
144            features: vec!["eip1559".to_string()],
145            symbol: "ETH".to_string(),
146            gas_price_cache: None,
147        };
148
149        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
150        assert!(result.is_ok());
151        assert_eq!(result.unwrap(), Some(1_000_000_000u128));
152    }
153
154    #[tokio::test]
155    async fn test_default_estimator_polygon() {
156        let mut mock_provider = MockEvmProviderTrait::new();
157        mock_provider
158            .expect_get_gas_price()
159            .times(1)
160            .returning(|| async { Ok(137_000_000_000u128) }.boxed());
161
162        let fetcher = DefaultGasPriceFetcher;
163        let network = EvmNetwork {
164            network: "polygon".to_string(),
165            rpc_urls: vec![crate::models::RpcConfig::new(
166                "https://polygon-rpc.com".to_string(),
167            )],
168            explorer_urls: None,
169            average_blocktime_ms: 2000,
170            is_testnet: false,
171            tags: vec![],
172            chain_id: 137,
173            required_confirmations: 10,
174            features: vec!["eip1559".to_string()],
175            symbol: "MATIC".to_string(),
176            gas_price_cache: None,
177        };
178
179        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
180        assert!(result.is_ok());
181        assert_eq!(result.unwrap(), Some(137_000_000_000u128));
182    }
183}