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{:x}", g)),
28 "gasPrice": tx.gas_price.map(|gp| format!("0x{:x}", gp)),
29 "maxFeePerGas": tx.max_fee_per_gas.map(|mfpg| format!("0x{:x}", mfpg)),
30 "maxPriorityFeePerGas": tx.max_priority_fee_per_gas.map(|mpfpg| format!("0x{:x}", mpfpg)),
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: {}",
96 e
97 )))
98 }
99 };
100
101 original_params.total_cost = zkevm_fee_estimate;
104
105 Ok(original_params)
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::{models::U256, services::provider::evm::MockEvmProviderTrait};
113 use mockall::predicate::*;
114
115 #[tokio::test]
116 async fn test_polygon_zkevm_price_handler_legacy() {
117 let mut mock_provider = MockEvmProviderTrait::new();
119
120 mock_provider
122 .expect_raw_request_dyn()
123 .with(eq("zkevm_estimateFee"), always())
124 .returning(|_, _| {
125 Box::pin(async move {
126 Ok(serde_json::json!("0x1c6bf52634000")) })
128 });
129
130 let handler = PolygonZKEvmPriceHandler::new(mock_provider);
131
132 let tx = EvmTransactionData {
134 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
135 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
136 value: U256::from(1_000_000_000_000_000_000u128), data: Some("0x1234567890abcdef".to_string()), gas_limit: Some(21000),
139 gas_price: Some(20_000_000_000), max_fee_per_gas: None,
141 max_priority_fee_per_gas: None,
142 speed: None,
143 nonce: None,
144 chain_id: 1101,
145 hash: None,
146 signature: None,
147 raw: None,
148 };
149
150 let original_params = PriceParams {
152 gas_price: Some(20_000_000_000), max_fee_per_gas: None,
154 max_priority_fee_per_gas: None,
155 is_min_bumped: None,
156 extra_fee: None,
157 total_cost: U256::ZERO,
158 };
159
160 let result = handler.handle_price_params(&tx, original_params).await;
162
163 assert!(result.is_ok());
164 let handled_params = result.unwrap();
165
166 assert_eq!(handled_params.gas_price.unwrap(), 20_000_000_000); assert_eq!(
171 handled_params.total_cost,
172 U256::from(500_000_000_000_000u128)
173 );
174 }
175
176 #[tokio::test]
177 async fn test_polygon_zkevm_price_handler_eip1559() {
178 let mut mock_provider = MockEvmProviderTrait::new();
180
181 mock_provider
183 .expect_raw_request_dyn()
184 .with(eq("zkevm_estimateFee"), always())
185 .returning(|_, _| {
186 Box::pin(async move {
187 Ok(serde_json::json!("0x2aa1efb94e000")) })
189 });
190
191 let handler = PolygonZKEvmPriceHandler::new(mock_provider);
192
193 let tx = EvmTransactionData {
195 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
196 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
197 value: U256::from(1_000_000_000_000_000_000u128), data: Some("0x1234567890abcdef".to_string()), gas_limit: Some(21000),
200 gas_price: None,
201 max_fee_per_gas: Some(30_000_000_000), max_priority_fee_per_gas: Some(2_000_000_000), speed: None,
204 nonce: None,
205 chain_id: 1101,
206 hash: None,
207 signature: None,
208 raw: None,
209 };
210
211 let original_params = PriceParams {
213 gas_price: None,
214 max_fee_per_gas: Some(30_000_000_000), max_priority_fee_per_gas: Some(2_000_000_000), is_min_bumped: None,
217 extra_fee: None,
218 total_cost: U256::ZERO,
219 };
220
221 let result = handler.handle_price_params(&tx, original_params).await;
223
224 assert!(result.is_ok());
225 let handled_params = result.unwrap();
226
227 assert_eq!(handled_params.max_fee_per_gas.unwrap(), 30_000_000_000); assert_eq!(
230 handled_params.max_priority_fee_per_gas.unwrap(),
231 2_000_000_000
232 ); assert_eq!(
236 handled_params.total_cost,
237 U256::from(750_000_000_000_000u128)
238 );
239 }
240
241 #[tokio::test]
242 async fn test_polygon_zkevm_fee_estimation_integration() {
243 let mut mock_provider_no_data = MockEvmProviderTrait::new();
245 mock_provider_no_data
246 .expect_raw_request_dyn()
247 .with(eq("zkevm_estimateFee"), always())
248 .returning(|_, _| {
249 Box::pin(async move {
250 Ok(serde_json::json!("0xbefe6f672000")) })
252 });
253
254 let handler_no_data = PolygonZKEvmPriceHandler::new(mock_provider_no_data);
255
256 let empty_tx = EvmTransactionData {
257 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
258 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
259 value: U256::from(1_000_000_000_000_000_000u128),
260 data: None,
261 gas_limit: Some(21000),
262 gas_price: Some(15_000_000_000), max_fee_per_gas: None,
264 max_priority_fee_per_gas: None,
265 speed: None,
266 nonce: None,
267 chain_id: 1101,
268 hash: None,
269 signature: None,
270 raw: None,
271 };
272
273 let original_params = PriceParams {
274 gas_price: Some(15_000_000_000),
275 max_fee_per_gas: None,
276 max_priority_fee_per_gas: None,
277 is_min_bumped: None,
278 extra_fee: None,
279 total_cost: U256::ZERO,
280 };
281
282 let result = handler_no_data
283 .handle_price_params(&empty_tx, original_params)
284 .await;
285 assert!(result.is_ok());
286 let handled_params = result.unwrap();
287
288 assert_eq!(handled_params.gas_price.unwrap(), 15_000_000_000);
290 assert_eq!(
291 handled_params.total_cost,
292 U256::from(210_000_000_000_000u128)
293 );
294
295 let mut mock_provider_with_data = MockEvmProviderTrait::new();
297 mock_provider_with_data
298 .expect_raw_request_dyn()
299 .with(eq("zkevm_estimateFee"), always())
300 .returning(|_, _| {
301 Box::pin(async move {
302 Ok(serde_json::json!("0x16bcc41e90000")) })
304 });
305
306 let handler_with_data = PolygonZKEvmPriceHandler::new(mock_provider_with_data);
307
308 let data_tx = EvmTransactionData {
309 data: Some("0x1234567890abcdef".to_string()), ..empty_tx
311 };
312
313 let original_params_with_data = PriceParams {
314 gas_price: Some(15_000_000_000),
315 max_fee_per_gas: None,
316 max_priority_fee_per_gas: None,
317 is_min_bumped: None,
318 extra_fee: None,
319 total_cost: U256::ZERO,
320 };
321
322 let result_with_data = handler_with_data
323 .handle_price_params(&data_tx, original_params_with_data)
324 .await;
325 assert!(result_with_data.is_ok());
326 let handled_params_with_data = result_with_data.unwrap();
327
328 assert!(handled_params_with_data.total_cost > handled_params.total_cost);
330 assert_eq!(
331 handled_params_with_data.total_cost,
332 U256::from(400_000_000_000_000u128)
333 );
334 }
335
336 #[tokio::test]
337 async fn test_polygon_zkevm_uses_gas_price_when_not_set() {
338 let mut mock_provider = MockEvmProviderTrait::new();
340 mock_provider
341 .expect_raw_request_dyn()
342 .with(eq("zkevm_estimateFee"), always())
343 .returning(|_, _| {
344 Box::pin(async move {
345 Ok(serde_json::json!("0x221b262dd8000")) })
347 });
348
349 let handler = PolygonZKEvmPriceHandler::new(mock_provider);
350
351 let tx = EvmTransactionData {
353 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
354 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
355 value: U256::from(1_000_000_000_000_000_000u128),
356 data: Some("0x1234".to_string()),
357 gas_limit: Some(21000),
358 gas_price: None, max_fee_per_gas: None,
360 max_priority_fee_per_gas: None,
361 speed: None,
362 nonce: None,
363 chain_id: 1101,
364 hash: None,
365 signature: None,
366 raw: None,
367 };
368
369 let original_params = PriceParams {
370 gas_price: None, max_fee_per_gas: None,
372 max_priority_fee_per_gas: None,
373 is_min_bumped: None,
374 extra_fee: None,
375 total_cost: U256::ZERO,
376 };
377
378 let result = handler.handle_price_params(&tx, original_params).await;
379 assert!(result.is_ok());
380 let handled_params = result.unwrap();
381
382 assert!(handled_params.gas_price.is_none());
384 assert_eq!(
385 handled_params.total_cost,
386 U256::from(600_000_000_000_000u128)
387 );
388 }
389
390 #[tokio::test]
391 async fn test_polygon_zkevm_method_not_available() {
392 let mut mock_provider = MockEvmProviderTrait::new();
394 mock_provider
395 .expect_raw_request_dyn()
396 .with(eq("zkevm_estimateFee"), always())
397 .returning(|_, _| {
398 Box::pin(async move {
399 Err(ProviderError::RpcErrorCode {
400 code: StandardJsonRpcError::MethodNotFound.code(),
401 message: "Method not found".to_string(),
402 })
403 })
404 });
405
406 let handler = PolygonZKEvmPriceHandler::new(mock_provider);
407
408 let tx = EvmTransactionData {
409 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
410 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
411 value: U256::from(1_000_000_000_000_000_000u128),
412 data: Some("0x1234".to_string()),
413 gas_limit: Some(21000),
414 gas_price: Some(15_000_000_000), max_fee_per_gas: None,
416 max_priority_fee_per_gas: None,
417 speed: None,
418 nonce: None,
419 chain_id: 1101,
420 hash: None,
421 signature: None,
422 raw: None,
423 };
424
425 let original_params = PriceParams {
426 gas_price: Some(15_000_000_000),
427 max_fee_per_gas: None,
428 max_priority_fee_per_gas: None,
429 is_min_bumped: None,
430 extra_fee: None,
431 total_cost: U256::from(100_000),
432 };
433
434 let result = handler
435 .handle_price_params(&tx, original_params.clone())
436 .await;
437
438 assert!(result.is_ok());
439 let handled_params = result.unwrap();
440
441 assert_eq!(handled_params.gas_price, original_params.gas_price);
443 assert_eq!(
444 handled_params.max_fee_per_gas,
445 original_params.max_fee_per_gas
446 );
447 assert_eq!(
448 handled_params.max_priority_fee_per_gas,
449 original_params.max_priority_fee_per_gas
450 );
451 assert_eq!(handled_params.total_cost, original_params.total_cost);
452 }
453
454 #[tokio::test]
455 async fn test_polygon_zkevm_partial_method_not_available() {
456 let mut mock_provider = MockEvmProviderTrait::new();
458 mock_provider
459 .expect_raw_request_dyn()
460 .with(eq("zkevm_estimateFee"), always())
461 .returning(|_, _| {
462 Box::pin(async move {
463 Err(ProviderError::RpcErrorCode {
464 code: EthereumJsonRpcError::MethodNotSupported.code(),
465 message: "Method not supported".to_string(),
466 })
467 })
468 });
469
470 let handler = PolygonZKEvmPriceHandler::new(mock_provider);
471
472 let tx = EvmTransactionData {
473 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
474 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string()),
475 value: U256::from(1_000_000_000_000_000_000u128),
476 data: Some("0x1234".to_string()),
477 gas_limit: Some(21000),
478 gas_price: Some(15_000_000_000),
479 max_fee_per_gas: None,
480 max_priority_fee_per_gas: None,
481 speed: None,
482 nonce: None,
483 chain_id: 1101,
484 hash: None,
485 signature: None,
486 raw: None,
487 };
488
489 let original_params = PriceParams {
490 gas_price: Some(15_000_000_000),
491 max_fee_per_gas: None,
492 max_priority_fee_per_gas: None,
493 is_min_bumped: None,
494 extra_fee: None,
495 total_cost: U256::from(100_000),
496 };
497
498 let result = handler
499 .handle_price_params(&tx, original_params.clone())
500 .await;
501
502 assert!(result.is_ok());
503 let handled_params = result.unwrap();
504
505 assert_eq!(handled_params.gas_price, original_params.gas_price);
507 assert_eq!(
508 handled_params.max_fee_per_gas,
509 original_params.max_fee_per_gas
510 );
511 assert_eq!(
512 handled_params.max_priority_fee_per_gas,
513 original_params.max_priority_fee_per_gas
514 );
515 assert_eq!(handled_params.total_cost, original_params.total_cost);
516 }
517
518 #[test]
519 fn test_build_zkevm_transaction_params() {
520 let tx = EvmTransactionData {
522 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
523 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string()),
524 value: U256::from(1000000000000000000u64), data: Some("0x1234567890abcdef".to_string()),
526 gas_limit: Some(21000),
527 gas_price: Some(20000000000), max_fee_per_gas: Some(30000000000), max_priority_fee_per_gas: Some(2000000000), speed: None,
531 nonce: Some(42),
532 chain_id: 1101,
533 hash: None,
534 signature: None,
535 raw: None,
536 };
537
538 let params = build_zkevm_transaction_params(&tx);
539
540 assert_eq!(params["from"], "0x742d35Cc6634C0532925a3b844Bc454e4438f44e");
542 assert_eq!(params["to"], "0x742d35Cc6634C0532925a3b844Bc454e4438f44f");
543 assert_eq!(params["value"], "0xde0b6b3a7640000"); assert_eq!(params["data"], "0x1234567890abcdef");
545 assert_eq!(params["gas"], "0x5208"); assert_eq!(params["gasPrice"], "0x4a817c800"); assert_eq!(params["maxFeePerGas"], "0x6fc23ac00"); assert_eq!(params["maxPriorityFeePerGas"], "0x77359400"); let minimal_tx = EvmTransactionData {
552 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
553 to: None,
554 value: U256::ZERO,
555 data: None,
556 gas_limit: None,
557 gas_price: None,
558 max_fee_per_gas: None,
559 max_priority_fee_per_gas: None,
560 speed: None,
561 nonce: None,
562 chain_id: 1101,
563 hash: None,
564 signature: None,
565 raw: None,
566 };
567
568 let minimal_params = build_zkevm_transaction_params(&minimal_tx);
569
570 assert_eq!(
571 minimal_params["from"],
572 "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
573 );
574 assert_eq!(minimal_params["to"], serde_json::Value::Null); assert_eq!(minimal_params["value"], "0x0");
576 assert_eq!(minimal_params["data"], "0x");
577 assert_eq!(minimal_params["gas"], serde_json::Value::Null);
578 assert_eq!(minimal_params["gasPrice"], serde_json::Value::Null);
579 assert_eq!(minimal_params["maxFeePerGas"], serde_json::Value::Null);
580 assert_eq!(
581 minimal_params["maxPriorityFeePerGas"],
582 serde_json::Value::Null
583 );
584
585 let tx_without_prefix = EvmTransactionData {
587 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
588 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string()),
589 value: U256::ZERO,
590 data: Some("abcdef1234".to_string()), gas_limit: None,
592 gas_price: None,
593 max_fee_per_gas: None,
594 max_priority_fee_per_gas: None,
595 speed: None,
596 nonce: None,
597 chain_id: 1101,
598 hash: None,
599 signature: None,
600 raw: None,
601 };
602
603 let params_no_prefix = build_zkevm_transaction_params(&tx_without_prefix);
604 assert_eq!(params_no_prefix["data"], "0xabcdef1234"); }
606}