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!["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}