openzeppelin_relayer/services/gas/fetchers/
polygon_zkevm.rs1use crate::{
8 models::EvmNetwork,
9 services::provider::{evm::EvmProviderTrait, ProviderError},
10};
11use tracing::{debug, error, info, warn};
12
13#[derive(Debug, Clone)]
15pub struct PolygonZkEvmGasPriceFetcher;
16
17impl PolygonZkEvmGasPriceFetcher {
18 pub async fn fetch_gas_price<P: EvmProviderTrait>(
20 &self,
21 provider: &P,
22 network: &EvmNetwork,
23 ) -> Result<Option<u128>, ProviderError> {
24 if let Some(zkevm_price) = self.try_zkevm_fetch(provider, network).await? {
25 return Ok(Some(zkevm_price));
26 }
27 self.fallback_to_standard(provider, network).await
28 }
29
30 async fn try_zkevm_fetch<P: EvmProviderTrait>(
32 &self,
33 provider: &P,
34 network: &EvmNetwork,
35 ) -> Result<Option<u128>, ProviderError> {
36 let result = provider
37 .raw_request_dyn("zkevm_estimateGasPrice", serde_json::Value::Array(vec![]))
38 .await;
39
40 match result {
41 Ok(response) => self.parse_zkevm_response(response, network.chain_id),
42 Err(ProviderError::RpcErrorCode { code, .. }) if code == -32601 || code == -32004 => {
43 debug!(
44 "zkEVM gas price method not available for chain_id {} (error code: {})",
45 network.chain_id, code
46 );
47 Ok(None)
48 }
49 Err(e) => {
50 debug!(
51 "zkEVM gas price estimation failed for chain_id {}: {}",
52 network.chain_id, e
53 );
54 Ok(None)
55 }
56 }
57 }
58
59 fn parse_zkevm_response(
61 &self,
62 response: serde_json::Value,
63 chain_id: u64,
64 ) -> Result<Option<u128>, ProviderError> {
65 let Some(gas_price_hex) = response.as_str() else {
66 warn!(
67 "Invalid zkEVM gas price response format for chain_id {}",
68 chain_id
69 );
70 return Ok(None);
71 };
72
73 match u128::from_str_radix(gas_price_hex.trim_start_matches("0x"), 16) {
74 Ok(gas_price) => {
75 info!(
76 "zkEVM gas price estimated for chain_id {}: {} wei",
77 chain_id, gas_price
78 );
79 Ok(Some(gas_price))
80 }
81 Err(e) => {
82 warn!(
83 "Failed to parse zkEVM gas price response for chain_id {}: {}",
84 chain_id, e
85 );
86 Ok(None)
87 }
88 }
89 }
90
91 async fn fallback_to_standard<P: EvmProviderTrait>(
93 &self,
94 provider: &P,
95 network: &EvmNetwork,
96 ) -> Result<Option<u128>, ProviderError> {
97 match provider.get_gas_price().await {
98 Ok(standard_price) => {
99 info!(
100 "Using standard gas price fallback for zkEVM chain_id {}: {} wei",
101 network.chain_id, standard_price
102 );
103 Ok(Some(standard_price))
104 }
105 Err(e) => {
106 error!(
107 "Both zkEVM and standard gas price methods failed for chain_id {}",
108 network.chain_id
109 );
110 Err(e)
111 }
112 }
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::{constants::POLYGON_ZKEVM_TAG, services::provider::evm::MockEvmProviderTrait};
120 use futures::FutureExt;
121 use mockall::predicate::*;
122
123 fn create_zkevm_network() -> EvmNetwork {
124 EvmNetwork {
125 network: "polygon-zkevm".to_string(),
126 rpc_urls: vec![crate::models::RpcConfig::new(
127 "https://zkevm-rpc.com".to_string(),
128 )],
129 explorer_urls: None,
130 average_blocktime_ms: 2000,
131 is_testnet: false,
132 tags: vec![POLYGON_ZKEVM_TAG.to_string()],
133 chain_id: 1101,
134 required_confirmations: 1,
135 features: vec!["eip1559".to_string()],
136 symbol: "ETH".to_string(),
137 gas_price_cache: None,
138 }
139 }
140
141 #[tokio::test]
142 async fn test_zkevm_fetcher_success() {
143 let mut mock_provider = MockEvmProviderTrait::new();
144 mock_provider
145 .expect_raw_request_dyn()
146 .with(
147 eq("zkevm_estimateGasPrice"),
148 eq(serde_json::Value::Array(vec![])),
149 )
150 .times(1)
151 .returning(|_, _| {
152 async { Ok(serde_json::Value::String("0x174876e800".to_string())) }.boxed()
153 });
154
155 let fetcher = PolygonZkEvmGasPriceFetcher;
156 let network = create_zkevm_network();
157
158 let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
159 assert!(result.is_ok());
160 assert_eq!(result.unwrap(), Some(100_000_000_000u128));
161 }
162
163 #[tokio::test]
164 async fn test_zkevm_estimator_method_not_available() {
165 let mut mock_provider = MockEvmProviderTrait::new();
166 mock_provider
167 .expect_raw_request_dyn()
168 .with(
169 eq("zkevm_estimateGasPrice"),
170 eq(serde_json::Value::Array(vec![])),
171 )
172 .times(1)
173 .returning(|_, _| {
174 async {
175 Err(ProviderError::RpcErrorCode {
176 code: -32601,
177 message: "Method not found".to_string(),
178 })
179 }
180 .boxed()
181 });
182 mock_provider
183 .expect_get_gas_price()
184 .times(1)
185 .returning(|| async { Ok(20_000_000_000u128) }.boxed());
186
187 let fetcher = PolygonZkEvmGasPriceFetcher;
188 let network = create_zkevm_network();
189
190 let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
191 assert!(result.is_ok());
192 assert_eq!(result.unwrap(), Some(20_000_000_000u128));
193 }
194
195 #[tokio::test]
196 async fn test_zkevm_estimator_invalid_response() {
197 let mut mock_provider = MockEvmProviderTrait::new();
198 mock_provider
199 .expect_raw_request_dyn()
200 .with(
201 eq("zkevm_estimateGasPrice"),
202 eq(serde_json::Value::Array(vec![])),
203 )
204 .times(1)
205 .returning(|_, _| {
206 async { Ok(serde_json::Value::Number(serde_json::Number::from(123))) }.boxed()
207 });
208 mock_provider
209 .expect_get_gas_price()
210 .times(1)
211 .returning(|| async { Ok(15_000_000_000u128) }.boxed());
212
213 let fetcher = PolygonZkEvmGasPriceFetcher;
214 let network = create_zkevm_network();
215
216 let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
217 assert!(result.is_ok());
218 assert_eq!(result.unwrap(), Some(15_000_000_000u128));
219 }
220
221 #[tokio::test]
222 async fn test_zkevm_estimator_invalid_hex_response() {
223 let mut mock_provider = MockEvmProviderTrait::new();
224 mock_provider
225 .expect_raw_request_dyn()
226 .with(
227 eq("zkevm_estimateGasPrice"),
228 eq(serde_json::Value::Array(vec![])),
229 )
230 .times(1)
231 .returning(|_, _| {
232 async { Ok(serde_json::Value::String("invalid_hex".to_string())) }.boxed()
233 });
234 mock_provider
235 .expect_get_gas_price()
236 .times(1)
237 .returning(|| async { Ok(18_000_000_000u128) }.boxed());
238
239 let fetcher = PolygonZkEvmGasPriceFetcher;
240 let network = create_zkevm_network();
241
242 let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
243 assert!(result.is_ok());
244 assert_eq!(result.unwrap(), Some(18_000_000_000u128));
245 }
246
247 #[tokio::test]
248 async fn test_zkevm_estimator_other_error() {
249 let mut mock_provider = MockEvmProviderTrait::new();
250 mock_provider
251 .expect_raw_request_dyn()
252 .with(
253 eq("zkevm_estimateGasPrice"),
254 eq(serde_json::Value::Array(vec![])),
255 )
256 .times(1)
257 .returning(|_, _| {
258 async { Err(ProviderError::Other("Network timeout".to_string())) }.boxed()
259 });
260 mock_provider
261 .expect_get_gas_price()
262 .times(1)
263 .returning(|| async { Ok(22_000_000_000u128) }.boxed());
264
265 let fetcher = PolygonZkEvmGasPriceFetcher;
266 let network = create_zkevm_network();
267
268 let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
269 assert!(result.is_ok());
270 assert_eq!(result.unwrap(), Some(22_000_000_000u128));
271 }
272
273 #[tokio::test]
274 async fn test_zkevm_estimator_both_methods_fail() {
275 let mut mock_provider = MockEvmProviderTrait::new();
276 mock_provider
277 .expect_raw_request_dyn()
278 .with(
279 eq("zkevm_estimateGasPrice"),
280 eq(serde_json::Value::Array(vec![])),
281 )
282 .times(1)
283 .returning(|_, _| {
284 async { Err(ProviderError::Other("zkEVM method failed".to_string())) }.boxed()
285 });
286 mock_provider
287 .expect_get_gas_price()
288 .times(1)
289 .returning(|| async { Err(ProviderError::Timeout) }.boxed());
290
291 let fetcher = PolygonZkEvmGasPriceFetcher;
292 let network = create_zkevm_network();
293
294 let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
295 assert!(result.is_err());
296 assert!(matches!(result.unwrap_err(), ProviderError::Timeout));
297 }
298
299 #[tokio::test]
300 async fn test_zkevm_estimator_hex_with_0x_prefix() {
301 let mut mock_provider = MockEvmProviderTrait::new();
302 mock_provider
303 .expect_raw_request_dyn()
304 .with(
305 eq("zkevm_estimateGasPrice"),
306 eq(serde_json::Value::Array(vec![])),
307 )
308 .times(1)
309 .returning(|_, _| {
310 async { Ok(serde_json::Value::String("0x3b9aca00".to_string())) }.boxed()
311 });
312
313 let fetcher = PolygonZkEvmGasPriceFetcher;
314 let network = create_zkevm_network();
315
316 let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
317 assert!(result.is_ok());
318 assert_eq!(result.unwrap(), Some(1_000_000_000u128));
319 }
320
321 #[tokio::test]
322 async fn test_zkevm_estimator_hex_without_0x_prefix() {
323 let mut mock_provider = MockEvmProviderTrait::new();
324 mock_provider
325 .expect_raw_request_dyn()
326 .with(
327 eq("zkevm_estimateGasPrice"),
328 eq(serde_json::Value::Array(vec![])),
329 )
330 .times(1)
331 .returning(|_, _| {
332 async { Ok(serde_json::Value::String("3b9aca00".to_string())) }.boxed()
333 });
334
335 let fetcher = PolygonZkEvmGasPriceFetcher;
336 let network = create_zkevm_network();
337
338 let result = fetcher.fetch_gas_price(&mock_provider, &network).await;
339 assert!(result.is_ok());
340 assert_eq!(result.unwrap(), Some(1_000_000_000u128));
341 }
342
343 #[test]
344 fn test_parse_zkevm_response_valid_hex() {
345 let fetcher = PolygonZkEvmGasPriceFetcher;
346 let response = serde_json::Value::String("0x174876e800".to_string());
347
348 let result = fetcher.parse_zkevm_response(response, 1101);
349 assert!(result.is_ok());
350 assert_eq!(result.unwrap(), Some(100_000_000_000u128));
351 }
352
353 #[test]
354 fn test_parse_zkevm_response_invalid_format() {
355 let fetcher = PolygonZkEvmGasPriceFetcher;
356 let response = serde_json::Value::Number(serde_json::Number::from(123));
357
358 let result = fetcher.parse_zkevm_response(response, 1101);
359 assert!(result.is_ok());
360 assert_eq!(result.unwrap(), None);
361 }
362
363 #[test]
364 fn test_parse_zkevm_response_invalid_hex() {
365 let fetcher = PolygonZkEvmGasPriceFetcher;
366 let response = serde_json::Value::String("not_hex".to_string());
367
368 let result = fetcher.parse_zkevm_response(response, 1101);
369 assert!(result.is_ok());
370 assert_eq!(result.unwrap(), None);
371 }
372}