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![crate::models::RpcConfig::new(
104                "https://zkevm-rpc.com".to_string(),
105            )],
106            explorer_urls: None,
107            average_blocktime_ms: 2000,
108            is_testnet: false,
109            tags: vec![POLYGON_ZKEVM_TAG.to_string()],
110            chain_id: 1101,
111            required_confirmations: 1,
112            features: vec!["eip1559".to_string()],
113            symbol: "ETH".to_string(),
114            gas_price_cache: None,
115        }
116    }
117
118    fn create_default_network() -> EvmNetwork {
119        EvmNetwork {
120            network: "ethereum".to_string(),
121            rpc_urls: vec![crate::models::RpcConfig::new(
122                "https://mainnet.infura.io".to_string(),
123            )],
124            explorer_urls: None,
125            average_blocktime_ms: 12000,
126            is_testnet: false,
127            tags: vec![],
128            chain_id: 1,
129            required_confirmations: 12,
130            features: vec!["eip1559".to_string()],
131            symbol: "ETH".to_string(),
132            gas_price_cache: None,
133        }
134    }
135
136    #[test]
137    fn test_factory_selects_zkevm_fetcher() {
138        let fetcher = GasPriceFetcherFactory::create_for_network(&create_zkevm_network());
139        assert!(matches!(fetcher, GasPriceFetcher::PolygonZkEvm(_)));
140    }
141
142    #[test]
143    fn test_factory_selects_default_fetcher() {
144        let fetcher = GasPriceFetcherFactory::create_for_network(&create_default_network());
145        assert!(matches!(fetcher, GasPriceFetcher::Default(_)));
146    }
147
148    #[tokio::test]
149    async fn test_enum_fetch_gas_price_default() {
150        let mut mock_provider = MockEvmProviderTrait::new();
151        mock_provider
152            .expect_get_gas_price()
153            .times(1)
154            .returning(|| async { Ok(25_000_000_000u128) }.boxed());
155
156        let fetcher = GasPriceFetcher::Default(
157            crate::services::gas::fetchers::default::DefaultGasPriceFetcher,
158        );
159        let network = create_default_network();
160
161        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
162        assert!(result.is_ok());
163        assert_eq!(result.unwrap(), Some(25_000_000_000u128));
164    }
165
166    #[tokio::test]
167    async fn test_enum_fetch_gas_price_zkevm() {
168        let mut mock_provider = MockEvmProviderTrait::new();
169        mock_provider
170            .expect_raw_request_dyn()
171            .with(
172                eq("zkevm_estimateGasPrice"),
173                eq(serde_json::Value::Array(vec![])),
174            )
175            .times(1)
176            .returning(|_, _| {
177                async { Ok(serde_json::Value::String("0x2540be400".to_string())) }.boxed()
178            });
179
180        let fetcher = GasPriceFetcher::PolygonZkEvm(
181            crate::services::gas::fetchers::polygon_zkevm::PolygonZkEvmGasPriceFetcher,
182        );
183        let network = create_zkevm_network();
184
185        let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
186        assert!(result.is_ok());
187        assert_eq!(result.unwrap(), Some(10_000_000_000u128));
188    }
189
190    #[tokio::test]
191    async fn test_factory_fetch_gas_price_success() {
192        let mut mock_provider = MockEvmProviderTrait::new();
193        mock_provider
194            .expect_get_gas_price()
195            .times(1)
196            .returning(|| async { Ok(30_000_000_000u128) }.boxed());
197
198        let network = create_default_network();
199        let result = GasPriceFetcherFactory::fetch_gas_price(&mock_provider, &network).await;
200
201        assert!(result.is_ok());
202        assert_eq!(result.unwrap(), 30_000_000_000u128);
203    }
204
205    #[tokio::test]
206    async fn test_factory_fetch_gas_price_estimator_returns_none() {
207        let mut mock_provider = MockEvmProviderTrait::new();
208        mock_provider
209            .expect_raw_request_dyn()
210            .with(
211                eq("zkevm_estimateGasPrice"),
212                eq(serde_json::Value::Array(vec![])),
213            )
214            .times(1)
215            .returning(|_, _| async { Ok(serde_json::Value::Null) }.boxed());
216        mock_provider
217            .expect_get_gas_price()
218            .times(1)
219            .returning(|| async { Err(ProviderError::Timeout) }.boxed());
220
221        let network = create_zkevm_network();
222        let result = GasPriceFetcherFactory::fetch_gas_price(&mock_provider, &network).await;
223
224        assert!(result.is_err());
225        assert!(matches!(result.unwrap_err(), ProviderError::Timeout));
226    }
227
228    #[tokio::test]
229    async fn test_factory_fetch_gas_price_provider_error() {
230        let mut mock_provider = MockEvmProviderTrait::new();
231        mock_provider.expect_get_gas_price().times(1).returning(|| {
232            async { Err(ProviderError::Other("Connection failed".to_string())) }.boxed()
233        });
234
235        let network = create_default_network();
236        let result = GasPriceFetcherFactory::fetch_gas_price(&mock_provider, &network).await;
237
238        assert!(result.is_err());
239        assert!(matches!(result.unwrap_err(), ProviderError::Other(_)));
240    }
241}