1use crate::{
2 domain::evm::PriceParams,
3 models::{EvmTransactionData, TransactionError, U256},
4 services::provider::{evm::EvmProviderTrait, ProviderError},
5 utils::{EthereumJsonRpcError, StandardJsonRpcError},
6};
7use serde_json;
8
9fn build_zkevm_transaction_params(tx: &EvmTransactionData) -> serde_json::Value {
20 serde_json::json!({
21 "from": tx.from,
22 "to": tx.to.clone(),
23 "value": format!("0x{:x}", tx.value),
24 "data": tx.data.as_ref().map(|d| {
25 if d.starts_with("0x") { d.clone() } else { format!("0x{d}") }
26 }).unwrap_or("0x".to_string()),
27 "gas": tx.gas_limit.map(|g| format!("0x{g:x}")),
28 "gasPrice": tx.gas_price.map(|gp| format!("0x{gp:x}")),
29 "maxFeePerGas": tx.max_fee_per_gas.map(|mfpg| format!("0x{mfpg:x}")),
30 "maxPriorityFeePerGas": tx.max_priority_fee_per_gas.map(|mpfpg| format!("0x{mpfpg:x}")),
31 })
32}
33
34#[derive(Debug, Clone)]
41pub struct PolygonZKEvmPriceHandler<P> {
42 provider: P,
43}
44
45impl<P: EvmProviderTrait> PolygonZKEvmPriceHandler<P> {
46 pub fn new(provider: P) -> Self {
47 Self { provider }
48 }
49
50 async fn zkevm_estimate_fee(&self, tx: &EvmTransactionData) -> Result<U256, ProviderError> {
58 let tx_params = build_zkevm_transaction_params(tx);
59
60 let result = self
61 .provider
62 .raw_request_dyn("zkevm_estimateFee", serde_json::json!([tx_params]))
63 .await?;
64
65 let fee_hex = result
66 .as_str()
67 .ok_or_else(|| ProviderError::Other("Invalid fee response".to_string()))?;
68
69 let fee = U256::from_str_radix(fee_hex.trim_start_matches("0x"), 16)
70 .map_err(|e| ProviderError::Other(format!("Failed to parse fee: {e}")))?;
71
72 Ok(fee)
73 }
74
75 pub async fn handle_price_params(
76 &self,
77 tx: &EvmTransactionData,
78 mut original_params: PriceParams,
79 ) -> Result<PriceParams, TransactionError> {
80 let zkevm_fee_estimate = self.zkevm_estimate_fee(tx).await;
82
83 let zkevm_fee_estimate = match zkevm_fee_estimate {
85 Err(ProviderError::RpcErrorCode { code, .. })
86 if code == StandardJsonRpcError::MethodNotFound.code()
87 || code == EthereumJsonRpcError::MethodNotSupported.code() =>
88 {
89 return Ok(original_params);
91 }
92 Ok(fee_estimate) => fee_estimate,
93 Err(e) => {
94 return Err(TransactionError::UnexpectedError(format!(
95 "Failed to estimate zkEVM fee: {e}"
96 )))
97 }
98 };
99
100 original_params.total_cost = zkevm_fee_estimate;
103
104 Ok(original_params)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::{models::U256, services::provider::evm::MockEvmProviderTrait};
112 use mockall::predicate::*;
113
114 #[tokio::test]
115 async fn test_polygon_zkevm_price_handler_legacy() {
116 let mut mock_provider = MockEvmProviderTrait::new();
118
119 mock_provider
121 .expect_raw_request_dyn()
122 .with(eq("zkevm_estimateFee"), always())
123 .returning(|_, _| {
124 Box::pin(async move {
125 Ok(serde_json::json!("0x1c6bf52634000")) })
127 });
128
129 let handler = PolygonZKEvmPriceHandler::new(mock_provider);
130
131 let tx = EvmTransactionData {
133 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
134 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
135 value: U256::from(1_000_000_000_000_000_000u128), data: Some("0x1234567890abcdef".to_string()), gas_limit: Some(21000),
138 gas_price: Some(20_000_000_000), max_fee_per_gas: None,
140 max_priority_fee_per_gas: None,
141 speed: None,
142 nonce: None,
143 chain_id: 1101,
144 hash: None,
145 signature: None,
146 raw: None,
147 };
148
149 let original_params = PriceParams {
151 gas_price: Some(20_000_000_000), max_fee_per_gas: None,
153 max_priority_fee_per_gas: None,
154 is_min_bumped: None,
155 extra_fee: None,
156 total_cost: U256::ZERO,
157 };
158
159 let result = handler.handle_price_params(&tx, original_params).await;
161
162 assert!(result.is_ok());
163 let handled_params = result.unwrap();
164
165 assert_eq!(handled_params.gas_price.unwrap(), 20_000_000_000); assert_eq!(
170 handled_params.total_cost,
171 U256::from(500_000_000_000_000u128)
172 );
173 }
174
175 #[tokio::test]
176 async fn test_polygon_zkevm_price_handler_eip1559() {
177 let mut mock_provider = MockEvmProviderTrait::new();
179
180 mock_provider
182 .expect_raw_request_dyn()
183 .with(eq("zkevm_estimateFee"), always())
184 .returning(|_, _| {
185 Box::pin(async move {
186 Ok(serde_json::json!("0x2aa1efb94e000")) })
188 });
189
190 let handler = PolygonZKEvmPriceHandler::new(mock_provider);
191
192 let tx = EvmTransactionData {
194 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
195 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
196 value: U256::from(1_000_000_000_000_000_000u128), data: Some("0x1234567890abcdef".to_string()), gas_limit: Some(21000),
199 gas_price: None,
200 max_fee_per_gas: Some(30_000_000_000), max_priority_fee_per_gas: Some(2_000_000_000), speed: None,
203 nonce: None,
204 chain_id: 1101,
205 hash: None,
206 signature: None,
207 raw: None,
208 };
209
210 let original_params = PriceParams {
212 gas_price: None,
213 max_fee_per_gas: Some(30_000_000_000), max_priority_fee_per_gas: Some(2_000_000_000), is_min_bumped: None,
216 extra_fee: None,
217 total_cost: U256::ZERO,
218 };
219
220 let result = handler.handle_price_params(&tx, original_params).await;
222
223 assert!(result.is_ok());
224 let handled_params = result.unwrap();
225
226 assert_eq!(handled_params.max_fee_per_gas.unwrap(), 30_000_000_000); assert_eq!(
229 handled_params.max_priority_fee_per_gas.unwrap(),
230 2_000_000_000
231 ); assert_eq!(
235 handled_params.total_cost,
236 U256::from(750_000_000_000_000u128)
237 );
238 }
239
240 #[tokio::test]
241 async fn test_polygon_zkevm_fee_estimation_integration() {
242 let mut mock_provider_no_data = MockEvmProviderTrait::new();
244 mock_provider_no_data
245 .expect_raw_request_dyn()
246 .with(eq("zkevm_estimateFee"), always())
247 .returning(|_, _| {
248 Box::pin(async move {
249 Ok(serde_json::json!("0xbefe6f672000")) })
251 });
252
253 let handler_no_data = PolygonZKEvmPriceHandler::new(mock_provider_no_data);
254
255 let empty_tx = EvmTransactionData {
256 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
257 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
258 value: U256::from(1_000_000_000_000_000_000u128),
259 data: None,
260 gas_limit: Some(21000),
261 gas_price: Some(15_000_000_000), max_fee_per_gas: None,
263 max_priority_fee_per_gas: None,
264 speed: None,
265 nonce: None,
266 chain_id: 1101,
267 hash: None,
268 signature: None,
269 raw: None,
270 };
271
272 let original_params = PriceParams {
273 gas_price: Some(15_000_000_000),
274 max_fee_per_gas: None,
275 max_priority_fee_per_gas: None,
276 is_min_bumped: None,
277 extra_fee: None,
278 total_cost: U256::ZERO,
279 };
280
281 let result = handler_no_data
282 .handle_price_params(&empty_tx, original_params)
283 .await;
284 assert!(result.is_ok());
285 let handled_params = result.unwrap();
286
287 assert_eq!(handled_params.gas_price.unwrap(), 15_000_000_000);
289 assert_eq!(
290 handled_params.total_cost,
291 U256::from(210_000_000_000_000u128)
292 );
293
294 let mut mock_provider_with_data = MockEvmProviderTrait::new();
296 mock_provider_with_data
297 .expect_raw_request_dyn()
298 .with(eq("zkevm_estimateFee"), always())
299 .returning(|_, _| {
300 Box::pin(async move {
301 Ok(serde_json::json!("0x16bcc41e90000")) })
303 });
304
305 let handler_with_data = PolygonZKEvmPriceHandler::new(mock_provider_with_data);
306
307 let data_tx = EvmTransactionData {
308 data: Some("0x1234567890abcdef".to_string()), ..empty_tx
310 };
311
312 let original_params_with_data = PriceParams {
313 gas_price: Some(15_000_000_000),
314 max_fee_per_gas: None,
315 max_priority_fee_per_gas: None,
316 is_min_bumped: None,
317 extra_fee: None,
318 total_cost: U256::ZERO,
319 };
320
321 let result_with_data = handler_with_data
322 .handle_price_params(&data_tx, original_params_with_data)
323 .await;
324 assert!(result_with_data.is_ok());
325 let handled_params_with_data = result_with_data.unwrap();
326
327 assert!(handled_params_with_data.total_cost > handled_params.total_cost);
329 assert_eq!(
330 handled_params_with_data.total_cost,
331 U256::from(400_000_000_000_000u128)
332 );
333 }
334
335 #[tokio::test]
336 async fn test_polygon_zkevm_uses_gas_price_when_not_set() {
337 let mut mock_provider = MockEvmProviderTrait::new();
339 mock_provider
340 .expect_raw_request_dyn()
341 .with(eq("zkevm_estimateFee"), always())
342 .returning(|_, _| {
343 Box::pin(async move {
344 Ok(serde_json::json!("0x221b262dd8000")) })
346 });
347
348 let handler = PolygonZKEvmPriceHandler::new(mock_provider);
349
350 let tx = EvmTransactionData {
352 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
353 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
354 value: U256::from(1_000_000_000_000_000_000u128),
355 data: Some("0x1234".to_string()),
356 gas_limit: Some(21000),
357 gas_price: None, max_fee_per_gas: None,
359 max_priority_fee_per_gas: None,
360 speed: None,
361 nonce: None,
362 chain_id: 1101,
363 hash: None,
364 signature: None,
365 raw: None,
366 };
367
368 let original_params = PriceParams {
369 gas_price: None, max_fee_per_gas: None,
371 max_priority_fee_per_gas: None,
372 is_min_bumped: None,
373 extra_fee: None,
374 total_cost: U256::ZERO,
375 };
376
377 let result = handler.handle_price_params(&tx, original_params).await;
378 assert!(result.is_ok());
379 let handled_params = result.unwrap();
380
381 assert!(handled_params.gas_price.is_none());
383 assert_eq!(
384 handled_params.total_cost,
385 U256::from(600_000_000_000_000u128)
386 );
387 }
388
389 #[tokio::test]
390 async fn test_polygon_zkevm_method_not_available() {
391 let mut mock_provider = MockEvmProviderTrait::new();
393 mock_provider
394 .expect_raw_request_dyn()
395 .with(eq("zkevm_estimateFee"), always())
396 .returning(|_, _| {
397 Box::pin(async move {
398 Err(ProviderError::RpcErrorCode {
399 code: StandardJsonRpcError::MethodNotFound.code(),
400 message: "Method not found".to_string(),
401 })
402 })
403 });
404
405 let handler = PolygonZKEvmPriceHandler::new(mock_provider);
406
407 let tx = EvmTransactionData {
408 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
409 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
410 value: U256::from(1_000_000_000_000_000_000u128),
411 data: Some("0x1234".to_string()),
412 gas_limit: Some(21000),
413 gas_price: Some(15_000_000_000), max_fee_per_gas: None,
415 max_priority_fee_per_gas: None,
416 speed: None,
417 nonce: None,
418 chain_id: 1101,
419 hash: None,
420 signature: None,
421 raw: None,
422 };
423
424 let original_params = PriceParams {
425 gas_price: Some(15_000_000_000),
426 max_fee_per_gas: None,
427 max_priority_fee_per_gas: None,
428 is_min_bumped: None,
429 extra_fee: None,
430 total_cost: U256::from(100_000),
431 };
432
433 let result = handler
434 .handle_price_params(&tx, original_params.clone())
435 .await;
436
437 assert!(result.is_ok());
438 let handled_params = result.unwrap();
439
440 assert_eq!(handled_params.gas_price, original_params.gas_price);
442 assert_eq!(
443 handled_params.max_fee_per_gas,
444 original_params.max_fee_per_gas
445 );
446 assert_eq!(
447 handled_params.max_priority_fee_per_gas,
448 original_params.max_priority_fee_per_gas
449 );
450 assert_eq!(handled_params.total_cost, original_params.total_cost);
451 }
452
453 #[tokio::test]
454 async fn test_polygon_zkevm_partial_method_not_available() {
455 let mut mock_provider = MockEvmProviderTrait::new();
457 mock_provider
458 .expect_raw_request_dyn()
459 .with(eq("zkevm_estimateFee"), always())
460 .returning(|_, _| {
461 Box::pin(async move {
462 Err(ProviderError::RpcErrorCode {
463 code: EthereumJsonRpcError::MethodNotSupported.code(),
464 message: "Method not supported".to_string(),
465 })
466 })
467 });
468
469 let handler = PolygonZKEvmPriceHandler::new(mock_provider);
470
471 let tx = EvmTransactionData {
472 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
473 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
474 value: U256::from(1_000_000_000_000_000_000u128),
475 data: Some("0x1234".to_string()),
476 gas_limit: Some(21000),
477 gas_price: Some(15_000_000_000),
478 max_fee_per_gas: None,
479 max_priority_fee_per_gas: None,
480 speed: None,
481 nonce: None,
482 chain_id: 1101,
483 hash: None,
484 signature: None,
485 raw: None,
486 };
487
488 let original_params = PriceParams {
489 gas_price: Some(15_000_000_000),
490 max_fee_per_gas: None,
491 max_priority_fee_per_gas: None,
492 is_min_bumped: None,
493 extra_fee: None,
494 total_cost: U256::from(100_000),
495 };
496
497 let result = handler
498 .handle_price_params(&tx, original_params.clone())
499 .await;
500
501 assert!(result.is_ok());
502 let handled_params = result.unwrap();
503
504 assert_eq!(handled_params.gas_price, original_params.gas_price);
506 assert_eq!(
507 handled_params.max_fee_per_gas,
508 original_params.max_fee_per_gas
509 );
510 assert_eq!(
511 handled_params.max_priority_fee_per_gas,
512 original_params.max_priority_fee_per_gas
513 );
514 assert_eq!(handled_params.total_cost, original_params.total_cost);
515 }
516
517 #[test]
518 fn test_build_zkevm_transaction_params() {
519 let tx = EvmTransactionData {
521 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
522 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string()),
523 value: U256::from(1000000000000000000u64), data: Some("0x1234567890abcdef".to_string()),
525 gas_limit: Some(21000),
526 gas_price: Some(20000000000), max_fee_per_gas: Some(30000000000), max_priority_fee_per_gas: Some(2000000000), speed: None,
530 nonce: Some(42),
531 chain_id: 1101,
532 hash: None,
533 signature: None,
534 raw: None,
535 };
536
537 let params = build_zkevm_transaction_params(&tx);
538
539 assert_eq!(params["from"], "0x742d35Cc6634C0532925a3b844Bc454e4438f44e");
541 assert_eq!(params["to"], "0x742d35Cc6634C0532925a3b844Bc454e4438f44f");
542 assert_eq!(params["value"], "0xde0b6b3a7640000"); assert_eq!(params["data"], "0x1234567890abcdef");
544 assert_eq!(params["gas"], "0x5208"); assert_eq!(params["gasPrice"], "0x4a817c800"); assert_eq!(params["maxFeePerGas"], "0x6fc23ac00"); assert_eq!(params["maxPriorityFeePerGas"], "0x77359400"); let minimal_tx = EvmTransactionData {
551 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
552 to: None,
553 value: U256::ZERO,
554 data: None,
555 gas_limit: None,
556 gas_price: None,
557 max_fee_per_gas: None,
558 max_priority_fee_per_gas: None,
559 speed: None,
560 nonce: None,
561 chain_id: 1101,
562 hash: None,
563 signature: None,
564 raw: None,
565 };
566
567 let minimal_params = build_zkevm_transaction_params(&minimal_tx);
568
569 assert_eq!(
570 minimal_params["from"],
571 "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
572 );
573 assert_eq!(minimal_params["to"], serde_json::Value::Null); assert_eq!(minimal_params["value"], "0x0");
575 assert_eq!(minimal_params["data"], "0x");
576 assert_eq!(minimal_params["gas"], serde_json::Value::Null);
577 assert_eq!(minimal_params["gasPrice"], serde_json::Value::Null);
578 assert_eq!(minimal_params["maxFeePerGas"], serde_json::Value::Null);
579 assert_eq!(
580 minimal_params["maxPriorityFeePerGas"],
581 serde_json::Value::Null
582 );
583
584 let tx_without_prefix = EvmTransactionData {
586 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
587 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string()),
588 value: U256::ZERO,
589 data: Some("abcdef1234".to_string()), gas_limit: None,
591 gas_price: None,
592 max_fee_per_gas: None,
593 max_priority_fee_per_gas: None,
594 speed: None,
595 nonce: None,
596 chain_id: 1101,
597 hash: None,
598 signature: None,
599 raw: None,
600 };
601
602 let params_no_prefix = build_zkevm_transaction_params(&tx_without_prefix);
603 assert_eq!(params_no_prefix["data"], "0xabcdef1234"); }
605}