openzeppelin_relayer/services/gas/fetchers/
mod.rs

1//! Gas price fetcher system for EVM networks.
2//!
3//! This module provides a flexible gas price fetcher framework that supports
4//! network-specific methods while maintaining a consistent interface.
5//! The system automatically selects the most appropriate fetcher strategy
6//! based on network characteristics.
7
8use crate::{
9    models::EvmNetwork,
10    services::provider::{evm::EvmProviderTrait, ProviderError},
11};
12use tracing::{debug, warn};
13
14pub mod default;
15pub mod polygon_zkevm;
16
17/// Gas price fetcher strategies for different network types.
18///
19/// Each variant encapsulates a specific fetcher strategy optimized for
20/// particular network characteristics or requirements.
21#[derive(Debug, Clone)]
22pub enum GasPriceFetcher {
23    /// Default EVM gas price fetcher using standard `eth_gasPrice`
24    Default(default::DefaultGasPriceFetcher),
25    PolygonZkEvm(polygon_zkevm::PolygonZkEvmGasPriceFetcher),
26}
27
28impl GasPriceFetcher {
29    /// Fetches gas price using the encapsulated strategy.
30    pub async fn fetch_gas_price<P: EvmProviderTrait>(
31        &self,
32        provider: &P,
33        network: &EvmNetwork,
34    ) -> Result<Option<u128>, ProviderError> {
35        match self {
36            GasPriceFetcher::Default(fetcher) => fetcher.fetch_gas_price(provider, network).await,
37            GasPriceFetcher::PolygonZkEvm(fetcher) => {
38                fetcher.fetch_gas_price(provider, network).await
39            }
40        }
41    }
42}
43
44/// Factory for creating network-appropriate gas price fetchers.
45pub struct GasPriceFetcherFactory;
46
47impl GasPriceFetcherFactory {
48    /// Creates the most suitable fetcher for the network.
49    pub fn create_for_network(network: &EvmNetwork) -> GasPriceFetcher {
50        if network.is_polygon_zkevm() {
51            GasPriceFetcher::PolygonZkEvm(polygon_zkevm::PolygonZkEvmGasPriceFetcher)
52        } else {
53            GasPriceFetcher::Default(default::DefaultGasPriceFetcher)
54        }
55    }
56
57    /// Fetches gas price using the best available method for the network.
58    pub async fn fetch_gas_price<P: EvmProviderTrait>(
59        provider: &P,
60        network: &EvmNetwork,
61    ) -> Result<u128, ProviderError> {
62        let gas_price_fetcher = Self::create_for_network(network);
63
64        match gas_price_fetcher.fetch_gas_price(provider, network).await {
65            Ok(Some(gas_price)) => {
66                debug!(
67                    "Gas price fetched for chain_id {}: {} wei",
68                    network.chain_id, gas_price
69                );
70                Ok(gas_price)
71            }
72            Ok(None) => {
73                warn!(
74                    "Fetcher returned None for supported network chain_id {}",
75                    network.chain_id
76                );
77                Err(ProviderError::Other(
78                    "Fetcher failed to provide gas price for supported network".to_string(),
79                ))
80            }
81            Err(e) => {
82                debug!(
83                    "Gas price fetch failed for chain_id {}: {}",
84                    network.chain_id, e
85                );
86                Err(e)
87            }
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::constants::POLYGON_ZKEVM_TAG;
96    use crate::services::provider::evm::MockEvmProviderTrait;
97    use futures::FutureExt;
98    use mockall::predicate::eq;
99
100    fn create_zkevm_network() -> EvmNetwork {
101        EvmNetwork {
102            network: "polygon-zkevm".to_string(),
103            rpc_urls: vec!["https://zkevm-rpc.com".to_string()],
104            explorer_urls: None,
105            average_blocktime_ms: 2000,
106            is_testnet: false,
107            tags: vec![POLYGON_ZKEVM_TAG.to_string()],
108            chain_id: 1101,
109            required_confirmations: 1,
110            features: vec!["eip1559".to_string()],
111            symbol: "ETH".to_string(),
112            gas_price_cache: None,
113        }
114    }
115
116    fn create_default_network() -> EvmNetwork {
117        EvmNetwork {
118            network: "ethereum".to_string(),
119            rpc_urls: vec!["https://mainnet.infura.io".to_string()],
120            explorer_urls: None,
121            average_blocktime_ms: 12000,
122            is_testnet: false,
123            tags: vec![],
124            chain_id: 1,
125            required_confirmations: 12,
126            features: vec!["eip1559".to_string()],
127            symbol: "ETH".to_string(),
128            gas_price_cache: None,
129        }
130    }
131
132    #[test]
133    fn test_factory_selects_zkevm_fetcher() {
134        let fetcher = GasPriceFetcherFactory::create_for_network(&create_zkevm_network());
135        assert!(matches!(fetcher, GasPriceFetcher::PolygonZkEvm(_)));
136    }
137
138    #[test]
139    fn test_factory_selects_default_fetcher() {
140        let fetcher = GasPriceFetcherFactory::create_for_network(&create_default_network());
141        assert!(matches!(fetcher, GasPriceFetcher::Default(_)));
142    }
143
144    #[tokio::test]
145    async fn test_enum_fetch_gas_price_default() {
146        let mut mock_provider = MockEvmProviderTrait::new();
147        mock_provider
148            .expect_get_gas_price()
149            .times(1)
150            .returning(|| async { Ok(25_000_000_000u128) }.boxed());
151
152        let fetcher = GasPriceFetcher::Default(
153            crate::services::gas::fetchers::default::DefaultGasPriceFetcher,
154        );
155        let network = create_default_network();
156
157        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
158        assert!(result.is_ok());
159        assert_eq!(result.unwrap(), Some(25_000_000_000u128));
160    }
161
162    #[tokio::test]
163    async fn test_enum_fetch_gas_price_zkevm() {
164        let mut mock_provider = MockEvmProviderTrait::new();
165        mock_provider
166            .expect_raw_request_dyn()
167            .with(
168                eq("zkevm_estimateGasPrice"),
169                eq(serde_json::Value::Array(vec![])),
170            )
171            .times(1)
172            .returning(|_, _| {
173                async { Ok(serde_json::Value::String("0x2540be400".to_string())) }.boxed()
174            });
175
176        let fetcher = GasPriceFetcher::PolygonZkEvm(
177            crate::services::gas::fetchers::polygon_zkevm::PolygonZkEvmGasPriceFetcher,
178        );
179        let network = create_zkevm_network();
180
181        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
182        assert!(result.is_ok());
183        assert_eq!(result.unwrap(), Some(10_000_000_000u128));
184    }
185
186    #[tokio::test]
187    async fn test_factory_fetch_gas_price_success() {
188        let mut mock_provider = MockEvmProviderTrait::new();
189        mock_provider
190            .expect_get_gas_price()
191            .times(1)
192            .returning(|| async { Ok(30_000_000_000u128) }.boxed());
193
194        let network = create_default_network();
195        let result = GasPriceFetcherFactory::fetch_gas_price(&mock_provider, &network).await;
196
197        assert!(result.is_ok());
198        assert_eq!(result.unwrap(), 30_000_000_000u128);
199    }
200
201    #[tokio::test]
202    async fn test_factory_fetch_gas_price_estimator_returns_none() {
203        let mut mock_provider = MockEvmProviderTrait::new();
204        mock_provider
205            .expect_raw_request_dyn()
206            .with(
207                eq("zkevm_estimateGasPrice"),
208                eq(serde_json::Value::Array(vec![])),
209            )
210            .times(1)
211            .returning(|_, _| async { Ok(serde_json::Value::Null) }.boxed());
212        mock_provider
213            .expect_get_gas_price()
214            .times(1)
215            .returning(|| async { Err(ProviderError::Timeout) }.boxed());
216
217        let network = create_zkevm_network();
218        let result = GasPriceFetcherFactory::fetch_gas_price(&mock_provider, &network).await;
219
220        assert!(result.is_err());
221        assert!(matches!(result.unwrap_err(), ProviderError::Timeout));
222    }
223
224    #[tokio::test]
225    async fn test_factory_fetch_gas_price_provider_error() {
226        let mut mock_provider = MockEvmProviderTrait::new();
227        mock_provider.expect_get_gas_price().times(1).returning(|| {
228            async { Err(ProviderError::Other("Connection failed".to_string())) }.boxed()
229        });
230
231        let network = create_default_network();
232        let result = GasPriceFetcherFactory::fetch_gas_price(&mock_provider, &network).await;
233
234        assert!(result.is_err());
235        assert!(matches!(result.unwrap_err(), ProviderError::Other(_)));
236    }
237}