openzeppelin_relayer/services/gas/fetchers/
mod.rs1use 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#[derive(Debug, Clone)]
22pub enum GasPriceFetcher {
23 Default(default::DefaultGasPriceFetcher),
25 PolygonZkEvm(polygon_zkevm::PolygonZkEvmGasPriceFetcher),
26}
27
28impl GasPriceFetcher {
29 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
44pub struct GasPriceFetcherFactory;
46
47impl GasPriceFetcherFactory {
48 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 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}