1use crate::{
6 constants::{DEFAULT_EVM_GAS_PRICE_CAP, DEFAULT_GAS_LIMIT},
7 domain::transaction::evm::price_calculator::{calculate_min_bump, PriceCalculatorTrait},
8 models::{
9 EvmTransactionData, EvmTransactionDataTrait, RelayerRepoModel, TransactionError, U256,
10 },
11};
12
13use super::PriceParams;
14
15pub fn has_explicit_prices(evm_data: &EvmTransactionData) -> bool {
25 evm_data.gas_price.is_some()
26 || evm_data.max_fee_per_gas.is_some()
27 || evm_data.max_priority_fee_per_gas.is_some()
28}
29
30pub fn check_transaction_compatibility(
41 old_evm_data: &EvmTransactionData,
42 new_evm_data: &EvmTransactionData,
43) -> Result<(), TransactionError> {
44 let old_is_legacy = old_evm_data.is_legacy();
45 let new_is_legacy = new_evm_data.is_legacy();
46 let new_is_eip1559 = new_evm_data.is_eip1559();
47
48 if !has_explicit_prices(new_evm_data) {
50 return Ok(());
51 }
52
53 if old_is_legacy && new_is_eip1559 {
55 return Err(TransactionError::ValidationError(
56 "Cannot replace legacy transaction with EIP1559 transaction".to_string(),
57 ));
58 }
59
60 if !old_is_legacy && new_is_legacy {
61 return Err(TransactionError::ValidationError(
62 "Cannot replace EIP1559 transaction with legacy transaction".to_string(),
63 ));
64 }
65
66 Ok(())
67}
68
69pub async fn determine_replacement_pricing<PC: PriceCalculatorTrait>(
83 old_evm_data: &EvmTransactionData,
84 new_evm_data: &EvmTransactionData,
85 relayer: &RelayerRepoModel,
86 price_calculator: &PC,
87 network_lacks_mempool: bool,
88) -> Result<PriceParams, TransactionError> {
89 check_transaction_compatibility(old_evm_data, new_evm_data)?;
91
92 if has_explicit_prices(new_evm_data) {
93 validate_explicit_price_bump(old_evm_data, new_evm_data, relayer, network_lacks_mempool)
96 } else {
97 calculate_replacement_price(
98 old_evm_data,
99 new_evm_data,
100 relayer,
101 price_calculator,
102 network_lacks_mempool,
103 )
104 .await
105 }
106}
107
108pub fn validate_explicit_price_bump(
121 old_evm_data: &EvmTransactionData,
122 new_evm_data: &EvmTransactionData,
123 relayer: &RelayerRepoModel,
124 network_lacks_mempool: bool,
125) -> Result<PriceParams, TransactionError> {
126 let mut price_params = PriceParams {
128 gas_price: new_evm_data.gas_price,
129 max_fee_per_gas: new_evm_data.max_fee_per_gas,
130 max_priority_fee_per_gas: new_evm_data.max_priority_fee_per_gas,
131 is_min_bumped: None,
132 extra_fee: None,
133 total_cost: U256::ZERO,
134 };
135
136 let gas_price_cap = relayer
138 .policies
139 .get_evm_policy()
140 .gas_price_cap
141 .unwrap_or(DEFAULT_EVM_GAS_PRICE_CAP);
142
143 if let Some(gas_price) = new_evm_data.gas_price {
145 if gas_price > gas_price_cap {
146 return Err(TransactionError::ValidationError(format!(
147 "Gas price {} exceeds gas price cap {}",
148 gas_price, gas_price_cap
149 )));
150 }
151 }
152
153 if let Some(max_fee) = new_evm_data.max_fee_per_gas {
154 if max_fee > gas_price_cap {
155 return Err(TransactionError::ValidationError(format!(
156 "Max fee per gas {} exceeds gas price cap {}",
157 max_fee, gas_price_cap
158 )));
159 }
160 }
161
162 if price_params.max_fee_per_gas.is_some() != price_params.max_priority_fee_per_gas.is_some() {
164 return Err(TransactionError::ValidationError(
165 "Partial EIP1559 transaction: both max_fee_per_gas and max_priority_fee_per_gas must be provided together".to_string(),
166 ));
167 }
168
169 if !network_lacks_mempool {
171 validate_price_bump_requirements(old_evm_data, new_evm_data)?;
172 }
173
174 if let (Some(max_fee), Some(max_priority)) = (
176 price_params.max_fee_per_gas,
177 price_params.max_priority_fee_per_gas,
178 ) {
179 if max_priority > max_fee {
180 return Err(TransactionError::ValidationError(
181 "Max priority fee cannot exceed max fee per gas".to_string(),
182 ));
183 }
184 }
185
186 let gas_limit = old_evm_data.gas_limit;
188 let value = new_evm_data.value;
189 let is_eip1559 = price_params.max_fee_per_gas.is_some();
190
191 price_params.total_cost = price_params.calculate_total_cost(
192 is_eip1559,
193 gas_limit.unwrap_or(DEFAULT_GAS_LIMIT),
194 value,
195 );
196 price_params.is_min_bumped = Some(true);
197
198 Ok(price_params)
199}
200
201fn validate_price_bump_requirements(
203 old_evm_data: &EvmTransactionData,
204 new_evm_data: &EvmTransactionData,
205) -> Result<(), TransactionError> {
206 let old_has_legacy_pricing = old_evm_data.gas_price.is_some();
207 let old_has_eip1559_pricing =
208 old_evm_data.max_fee_per_gas.is_some() && old_evm_data.max_priority_fee_per_gas.is_some();
209 let new_has_legacy_pricing = new_evm_data.gas_price.is_some();
210 let new_has_eip1559_pricing =
211 new_evm_data.max_fee_per_gas.is_some() && new_evm_data.max_priority_fee_per_gas.is_some();
212
213 if !new_has_legacy_pricing && !new_has_eip1559_pricing {
215 return Err(TransactionError::ValidationError(
216 "New transaction must have pricing data".to_string(),
217 ));
218 }
219
220 if !new_evm_data.is_legacy()
222 && new_evm_data.max_fee_per_gas.is_some() != new_evm_data.max_priority_fee_per_gas.is_some()
223 {
224 return Err(TransactionError::ValidationError(
225 "Partial EIP1559 transaction: both max_fee_per_gas and max_priority_fee_per_gas must be provided together".to_string(),
226 ));
227 }
228
229 if !old_has_legacy_pricing && !old_has_eip1559_pricing {
231 return Ok(());
232 }
233
234 let is_sufficient_bump = if let (Some(old_gas_price), Some(new_gas_price)) =
235 (old_evm_data.gas_price, new_evm_data.gas_price)
236 {
237 let min_required = calculate_min_bump(old_gas_price);
239 new_gas_price >= min_required
240 } else if let (Some(old_max_fee), Some(new_max_fee)) =
241 (old_evm_data.max_fee_per_gas, new_evm_data.max_fee_per_gas)
242 {
243 let min_required_max_fee = calculate_min_bump(old_max_fee);
245 let max_fee_sufficient = new_max_fee >= min_required_max_fee;
246
247 let priority_fee_sufficient = match (
249 old_evm_data.max_priority_fee_per_gas,
250 new_evm_data.max_priority_fee_per_gas,
251 ) {
252 (Some(old_priority), Some(new_priority)) => {
253 let min_required_priority = calculate_min_bump(old_priority);
254 new_priority >= min_required_priority
255 }
256 _ => {
257 return Err(TransactionError::ValidationError(
258 "Partial EIP1559 transaction: both max_fee_per_gas and max_priority_fee_per_gas must be provided together".to_string(),
259 ));
260 }
261 };
262
263 max_fee_sufficient && priority_fee_sufficient
264 } else {
265 return Err(TransactionError::ValidationError(
267 "Partial EIP1559 transaction: both max_fee_per_gas and max_priority_fee_per_gas must be provided together".to_string(),
268 ));
269 };
270
271 if !is_sufficient_bump {
272 return Err(TransactionError::ValidationError(
273 "Gas price increase does not meet minimum bump requirement".to_string(),
274 ));
275 }
276
277 Ok(())
278}
279
280pub async fn calculate_replacement_price<PC: PriceCalculatorTrait>(
294 old_evm_data: &EvmTransactionData,
295 new_evm_data: &EvmTransactionData,
296 relayer: &RelayerRepoModel,
297 price_calculator: &PC,
298 network_lacks_mempool: bool,
299) -> Result<PriceParams, TransactionError> {
300 let use_legacy = old_evm_data.is_legacy()
302 || relayer.policies.get_evm_policy().eip1559_pricing == Some(false);
303
304 let mut price_params = price_calculator
306 .get_transaction_price_params(new_evm_data, relayer)
307 .await?;
308
309 if network_lacks_mempool {
311 price_params.is_min_bumped = Some(true);
312 return Ok(price_params);
313 }
314
315 let is_sufficient_bump = if use_legacy {
318 if let (Some(old_gas_price), Some(new_gas_price)) =
319 (old_evm_data.gas_price, price_params.gas_price)
320 {
321 let min_required = calculate_min_bump(old_gas_price);
322 if new_gas_price < min_required {
323 price_params.gas_price = Some(min_required);
325 }
326 price_params.is_min_bumped = Some(true);
327 true
328 } else {
329 false
330 }
331 } else {
332 if let (Some(old_max_fee), Some(new_max_fee), Some(old_priority), Some(new_priority)) = (
334 old_evm_data.max_fee_per_gas,
335 price_params.max_fee_per_gas,
336 old_evm_data.max_priority_fee_per_gas,
337 price_params.max_priority_fee_per_gas,
338 ) {
339 let min_required = calculate_min_bump(old_max_fee);
340 let min_required_priority = calculate_min_bump(old_priority);
341 if new_max_fee < min_required {
342 price_params.max_fee_per_gas = Some(min_required);
343 }
344
345 if new_priority < min_required_priority {
346 price_params.max_priority_fee_per_gas = Some(min_required_priority);
347 }
348
349 price_params.is_min_bumped = Some(true);
350 true
351 } else {
352 false
353 }
354 };
355
356 if !is_sufficient_bump {
357 return Err(TransactionError::ValidationError(
358 "Unable to calculate sufficient price bump for speed-based replacement".to_string(),
359 ));
360 }
361
362 Ok(price_params)
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use crate::{
369 domain::transaction::evm::price_calculator::PriceCalculatorTrait,
370 models::{
371 evm::Speed, EvmTransactionData, RelayerEvmPolicy, RelayerNetworkPolicy,
372 RelayerRepoModel, TransactionError, U256,
373 },
374 };
375 use async_trait::async_trait;
376
377 struct MockPriceCalculator {
379 pub gas_price: Option<u128>,
380 pub max_fee_per_gas: Option<u128>,
381 pub max_priority_fee_per_gas: Option<u128>,
382 pub should_error: bool,
383 }
384
385 #[async_trait]
386 impl PriceCalculatorTrait for MockPriceCalculator {
387 async fn get_transaction_price_params(
388 &self,
389 _evm_data: &EvmTransactionData,
390 _relayer: &RelayerRepoModel,
391 ) -> Result<PriceParams, TransactionError> {
392 if self.should_error {
393 return Err(TransactionError::ValidationError("Mock error".to_string()));
394 }
395
396 Ok(PriceParams {
397 gas_price: self.gas_price,
398 max_fee_per_gas: self.max_fee_per_gas,
399 max_priority_fee_per_gas: self.max_priority_fee_per_gas,
400 is_min_bumped: Some(false),
401 extra_fee: None,
402 total_cost: U256::ZERO,
403 })
404 }
405
406 async fn calculate_bumped_gas_price(
407 &self,
408 _evm_data: &EvmTransactionData,
409 _relayer: &RelayerRepoModel,
410 ) -> Result<PriceParams, TransactionError> {
411 if self.should_error {
412 return Err(TransactionError::ValidationError("Mock error".to_string()));
413 }
414
415 Ok(PriceParams {
416 gas_price: self.gas_price,
417 max_fee_per_gas: self.max_fee_per_gas,
418 max_priority_fee_per_gas: self.max_priority_fee_per_gas,
419 is_min_bumped: Some(true),
420 extra_fee: None,
421 total_cost: U256::ZERO,
422 })
423 }
424 }
425
426 fn create_legacy_transaction_data() -> EvmTransactionData {
427 EvmTransactionData {
428 gas_price: Some(20_000_000_000), gas_limit: Some(21000),
430 nonce: Some(1),
431 value: U256::from(1000000000000000000u128), data: Some("0x".to_string()),
433 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
434 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
435 chain_id: 1,
436 hash: None,
437 signature: None,
438 speed: Some(Speed::Average),
439 max_fee_per_gas: None,
440 max_priority_fee_per_gas: None,
441 raw: None,
442 }
443 }
444
445 fn create_eip1559_transaction_data() -> EvmTransactionData {
446 EvmTransactionData {
447 gas_price: None,
448 gas_limit: Some(21000),
449 nonce: Some(1),
450 value: U256::from(1000000000000000000u128), data: Some("0x".to_string()),
452 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
453 to: Some("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed".to_string()),
454 chain_id: 1,
455 hash: None,
456 signature: None,
457 speed: Some(Speed::Average),
458 max_fee_per_gas: Some(30_000_000_000), max_priority_fee_per_gas: Some(2_000_000_000), raw: None,
461 }
462 }
463
464 fn create_test_relayer() -> RelayerRepoModel {
465 RelayerRepoModel {
466 id: "test-relayer".to_string(),
467 name: "Test Relayer".to_string(),
468 network: "ethereum".to_string(),
469 paused: false,
470 network_type: crate::models::NetworkType::Evm,
471 signer_id: "test-signer".to_string(),
472 policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
473 gas_price_cap: Some(100_000_000_000), eip1559_pricing: Some(true),
475 ..Default::default()
476 }),
477 address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
478 notification_id: None,
479 system_disabled: false,
480 custom_rpc_urls: None,
481 ..Default::default()
482 }
483 }
484
485 fn create_relayer_with_gas_cap(gas_cap: u128) -> RelayerRepoModel {
486 let mut relayer = create_test_relayer();
487 if let RelayerNetworkPolicy::Evm(ref mut policy) = relayer.policies {
488 policy.gas_price_cap = Some(gas_cap);
489 }
490 relayer
491 }
492
493 #[test]
494 fn test_has_explicit_prices() {
495 let legacy_tx = create_legacy_transaction_data();
496 assert!(has_explicit_prices(&legacy_tx));
497
498 let eip1559_tx = create_eip1559_transaction_data();
499 assert!(has_explicit_prices(&eip1559_tx));
500
501 let mut no_prices_tx = create_legacy_transaction_data();
502 no_prices_tx.gas_price = None;
503 assert!(!has_explicit_prices(&no_prices_tx));
504
505 let mut partial_eip1559 = create_legacy_transaction_data();
507 partial_eip1559.gas_price = None;
508 partial_eip1559.max_fee_per_gas = Some(30_000_000_000);
509 assert!(has_explicit_prices(&partial_eip1559));
510
511 let mut partial_priority = create_legacy_transaction_data();
513 partial_priority.gas_price = None;
514 partial_priority.max_priority_fee_per_gas = Some(2_000_000_000);
515 assert!(has_explicit_prices(&partial_priority));
516 }
517
518 #[test]
519 fn test_check_transaction_compatibility_success() {
520 let old_legacy = create_legacy_transaction_data();
522 let new_legacy = create_legacy_transaction_data();
523 assert!(check_transaction_compatibility(&old_legacy, &new_legacy).is_ok());
524
525 let old_eip1559 = create_eip1559_transaction_data();
527 let new_eip1559 = create_eip1559_transaction_data();
528 assert!(check_transaction_compatibility(&old_eip1559, &new_eip1559).is_ok());
529
530 let mut no_prices = create_legacy_transaction_data();
532 no_prices.gas_price = None;
533 assert!(check_transaction_compatibility(&old_legacy, &no_prices).is_ok());
534 }
535
536 #[test]
537 fn test_check_transaction_compatibility_failures() {
538 let old_legacy = create_legacy_transaction_data();
539 let old_eip1559 = create_eip1559_transaction_data();
540
541 let result = check_transaction_compatibility(&old_legacy, &old_eip1559);
543 assert!(result.is_err());
544
545 let result = check_transaction_compatibility(&old_eip1559, &old_legacy);
547 assert!(result.is_err());
548 }
549
550 #[test]
551 fn test_validate_explicit_price_bump_gas_price_cap() {
552 let old_tx = create_legacy_transaction_data();
553 let relayer = create_relayer_with_gas_cap(25_000_000_000);
554
555 let mut new_tx = create_legacy_transaction_data();
556 new_tx.gas_price = Some(50_000_000_000);
557
558 let result = validate_explicit_price_bump(&old_tx, &new_tx, &relayer, false);
559 assert!(result.is_err());
560
561 let mut new_eip1559 = create_eip1559_transaction_data();
562 new_eip1559.max_fee_per_gas = Some(50_000_000_000);
563
564 let old_eip1559 = create_eip1559_transaction_data();
565 let result = validate_explicit_price_bump(&old_eip1559, &new_eip1559, &relayer, false);
566 assert!(result.is_err());
567 }
568
569 #[test]
570 fn test_validate_explicit_price_bump_insufficient_bump() {
571 let relayer = create_test_relayer();
572
573 let old_legacy = create_legacy_transaction_data();
574 let mut new_legacy = create_legacy_transaction_data();
575 new_legacy.gas_price = Some(21_000_000_000); let result = validate_explicit_price_bump(&old_legacy, &new_legacy, &relayer, false);
578 assert!(result.is_err());
579
580 let old_eip1559 = create_eip1559_transaction_data();
581 let mut new_eip1559 = create_eip1559_transaction_data();
582 new_eip1559.max_fee_per_gas = Some(32_000_000_000); let result = validate_explicit_price_bump(&old_eip1559, &new_eip1559, &relayer, false);
585 assert!(result.is_err());
586 }
587
588 #[test]
589 fn test_validate_explicit_price_bump_sufficient_bump() {
590 let relayer = create_test_relayer();
591
592 let old_legacy = create_legacy_transaction_data();
593 let mut new_legacy = create_legacy_transaction_data();
594 new_legacy.gas_price = Some(22_000_000_000);
595
596 let result = validate_explicit_price_bump(&old_legacy, &new_legacy, &relayer, false);
597 assert!(result.is_ok());
598
599 let old_eip1559 = create_eip1559_transaction_data();
600 let mut new_eip1559 = create_eip1559_transaction_data();
601 new_eip1559.max_fee_per_gas = Some(33_000_000_000);
602 new_eip1559.max_priority_fee_per_gas = Some(3_000_000_000);
603
604 let result = validate_explicit_price_bump(&old_eip1559, &new_eip1559, &relayer, false);
605 assert!(result.is_ok());
606 }
607
608 #[test]
609 fn test_validate_explicit_price_bump_network_lacks_mempool() {
610 let relayer = create_test_relayer();
611 let old_legacy = create_legacy_transaction_data();
612 let mut new_legacy = create_legacy_transaction_data();
613 new_legacy.gas_price = Some(15_000_000_000); let result = validate_explicit_price_bump(&old_legacy, &new_legacy, &relayer, true);
617 assert!(result.is_ok());
618 }
619
620 #[test]
621 fn test_validate_explicit_price_bump_partial_eip1559_error() {
622 let relayer = create_test_relayer();
623 let old_eip1559 = create_eip1559_transaction_data();
624
625 let mut partial_max_fee = create_legacy_transaction_data();
627 partial_max_fee.gas_price = None;
628 partial_max_fee.max_fee_per_gas = Some(35_000_000_000);
629 partial_max_fee.max_priority_fee_per_gas = None;
630
631 let result = validate_explicit_price_bump(&old_eip1559, &partial_max_fee, &relayer, false);
632 assert!(result.is_err());
633
634 let mut partial_priority = create_legacy_transaction_data();
636 partial_priority.gas_price = None;
637 partial_priority.max_fee_per_gas = None;
638 partial_priority.max_priority_fee_per_gas = Some(3_000_000_000);
639
640 let result = validate_explicit_price_bump(&old_eip1559, &partial_priority, &relayer, false);
641 assert!(result.is_err());
642 }
643
644 #[test]
645 fn test_validate_explicit_price_bump_priority_fee_exceeds_max_fee() {
646 let relayer = create_test_relayer();
647 let old_eip1559 = create_eip1559_transaction_data();
648 let mut new_eip1559 = create_eip1559_transaction_data();
649 new_eip1559.max_fee_per_gas = Some(35_000_000_000);
650 new_eip1559.max_priority_fee_per_gas = Some(40_000_000_000);
651
652 let result = validate_explicit_price_bump(&old_eip1559, &new_eip1559, &relayer, false);
653 assert!(result.is_err());
654 }
655
656 #[test]
657 fn test_validate_explicit_price_bump_priority_fee_equals_max_fee() {
658 let relayer = create_test_relayer();
659 let old_eip1559 = create_eip1559_transaction_data();
660 let mut new_eip1559 = create_eip1559_transaction_data();
661 new_eip1559.max_fee_per_gas = Some(35_000_000_000);
662 new_eip1559.max_priority_fee_per_gas = Some(35_000_000_000);
663
664 let result = validate_explicit_price_bump(&old_eip1559, &new_eip1559, &relayer, false);
665 assert!(result.is_ok());
666 }
667
668 #[tokio::test]
669 async fn test_calculate_replacement_price_legacy_sufficient_market_price() {
670 let old_tx = create_legacy_transaction_data();
671 let new_tx = create_legacy_transaction_data();
672 let relayer = create_test_relayer();
673
674 let price_calculator = MockPriceCalculator {
675 gas_price: Some(25_000_000_000),
676 max_fee_per_gas: None,
677 max_priority_fee_per_gas: None,
678 should_error: false,
679 };
680
681 let result =
682 calculate_replacement_price(&old_tx, &new_tx, &relayer, &price_calculator, false).await;
683 assert!(result.is_ok());
684
685 let price_params = result.unwrap();
686 assert_eq!(price_params.gas_price, Some(25_000_000_000));
687 assert_eq!(price_params.is_min_bumped, Some(true));
688 }
689
690 #[tokio::test]
691 async fn test_calculate_replacement_price_legacy_insufficient_market_price() {
692 let old_tx = create_legacy_transaction_data();
693 let new_tx = create_legacy_transaction_data();
694 let relayer = create_test_relayer();
695
696 let price_calculator = MockPriceCalculator {
697 gas_price: Some(18_000_000_000), max_fee_per_gas: None,
699 max_priority_fee_per_gas: None,
700 should_error: false,
701 };
702
703 let result =
704 calculate_replacement_price(&old_tx, &new_tx, &relayer, &price_calculator, false).await;
705 assert!(result.is_ok());
706
707 let price_params = result.unwrap();
708 assert_eq!(price_params.gas_price, Some(22_000_000_000)); assert_eq!(price_params.is_min_bumped, Some(true));
710 }
711
712 #[tokio::test]
713 async fn test_calculate_replacement_price_eip1559_sufficient() {
714 let old_tx = create_eip1559_transaction_data();
715 let new_tx = create_eip1559_transaction_data();
716 let relayer = create_test_relayer();
717
718 let price_calculator = MockPriceCalculator {
719 gas_price: None,
720 max_fee_per_gas: Some(40_000_000_000),
721 max_priority_fee_per_gas: Some(3_000_000_000),
722 should_error: false,
723 };
724
725 let result =
726 calculate_replacement_price(&old_tx, &new_tx, &relayer, &price_calculator, false).await;
727 assert!(result.is_ok());
728
729 let price_params = result.unwrap();
730 assert_eq!(price_params.max_fee_per_gas, Some(40_000_000_000));
731 assert_eq!(price_params.is_min_bumped, Some(true));
732 }
733
734 #[tokio::test]
735 async fn test_calculate_replacement_price_eip1559_insufficient_with_priority_fee_bump() {
736 let mut old_tx = create_eip1559_transaction_data();
737 old_tx.max_fee_per_gas = Some(30_000_000_000);
738 old_tx.max_priority_fee_per_gas = Some(5_000_000_000);
739
740 let new_tx = create_eip1559_transaction_data();
741 let relayer = create_test_relayer();
742
743 let price_calculator = MockPriceCalculator {
744 gas_price: None,
745 max_fee_per_gas: Some(25_000_000_000), max_priority_fee_per_gas: Some(4_000_000_000), should_error: false,
748 };
749
750 let result =
751 calculate_replacement_price(&old_tx, &new_tx, &relayer, &price_calculator, false).await;
752 assert!(result.is_ok());
753
754 let price_params = result.unwrap();
755 assert_eq!(price_params.max_fee_per_gas, Some(33_000_000_000));
756
757 let expected_priority_bump = calculate_min_bump(5_000_000_000); let capped_priority = expected_priority_bump.min(33_000_000_000); assert_eq!(price_params.max_priority_fee_per_gas, Some(capped_priority));
761 }
762
763 #[tokio::test]
764 async fn test_calculate_replacement_price_network_lacks_mempool() {
765 let old_tx = create_legacy_transaction_data();
766 let new_tx = create_legacy_transaction_data();
767 let relayer = create_test_relayer();
768
769 let price_calculator = MockPriceCalculator {
770 gas_price: Some(15_000_000_000), max_fee_per_gas: None,
772 max_priority_fee_per_gas: None,
773 should_error: false,
774 };
775
776 let result =
777 calculate_replacement_price(&old_tx, &new_tx, &relayer, &price_calculator, true).await;
778 assert!(result.is_ok());
779
780 let price_params = result.unwrap();
781 assert_eq!(price_params.gas_price, Some(15_000_000_000)); assert_eq!(price_params.is_min_bumped, Some(true));
783 }
784
785 #[tokio::test]
786 async fn test_calculate_replacement_price_calculator_error() {
787 let old_tx = create_legacy_transaction_data();
788 let new_tx = create_legacy_transaction_data();
789 let relayer = create_test_relayer();
790
791 let price_calculator = MockPriceCalculator {
792 gas_price: None,
793 max_fee_per_gas: None,
794 max_priority_fee_per_gas: None,
795 should_error: true,
796 };
797
798 let result =
799 calculate_replacement_price(&old_tx, &new_tx, &relayer, &price_calculator, false).await;
800 assert!(result.is_err());
801 }
802
803 #[tokio::test]
804 async fn test_determine_replacement_pricing_explicit_prices() {
805 let old_tx = create_legacy_transaction_data();
806 let mut new_tx = create_legacy_transaction_data();
807 new_tx.gas_price = Some(25_000_000_000);
808 let relayer = create_test_relayer();
809
810 let price_calculator = MockPriceCalculator {
811 gas_price: Some(30_000_000_000),
812 max_fee_per_gas: None,
813 max_priority_fee_per_gas: None,
814 should_error: false,
815 };
816
817 let result =
818 determine_replacement_pricing(&old_tx, &new_tx, &relayer, &price_calculator, false)
819 .await;
820 assert!(result.is_ok());
821
822 let price_params = result.unwrap();
823 assert_eq!(price_params.gas_price, Some(25_000_000_000));
824 }
825
826 #[tokio::test]
827 async fn test_determine_replacement_pricing_market_prices() {
828 let old_tx = create_legacy_transaction_data();
829 let mut new_tx = create_legacy_transaction_data();
830 new_tx.gas_price = None;
831 let relayer = create_test_relayer();
832
833 let price_calculator = MockPriceCalculator {
834 gas_price: Some(30_000_000_000),
835 max_fee_per_gas: None,
836 max_priority_fee_per_gas: None,
837 should_error: false,
838 };
839
840 let result =
841 determine_replacement_pricing(&old_tx, &new_tx, &relayer, &price_calculator, false)
842 .await;
843 assert!(result.is_ok());
844
845 let price_params = result.unwrap();
846 assert_eq!(price_params.gas_price, Some(30_000_000_000));
847 }
848
849 #[tokio::test]
850 async fn test_determine_replacement_pricing_compatibility_error() {
851 let old_legacy = create_legacy_transaction_data();
852 let new_eip1559 = create_eip1559_transaction_data();
853 let relayer = create_test_relayer();
854
855 let price_calculator = MockPriceCalculator {
856 gas_price: None,
857 max_fee_per_gas: None,
858 max_priority_fee_per_gas: None,
859 should_error: false,
860 };
861
862 let result = determine_replacement_pricing(
863 &old_legacy,
864 &new_eip1559,
865 &relayer,
866 &price_calculator,
867 false,
868 )
869 .await;
870 assert!(result.is_err());
871 }
872
873 #[test]
874 fn test_validate_price_bump_requirements_legacy() {
875 let old_tx = create_legacy_transaction_data();
876
877 let mut new_tx_sufficient = create_legacy_transaction_data();
878 new_tx_sufficient.gas_price = Some(22_000_000_000);
879 assert!(validate_price_bump_requirements(&old_tx, &new_tx_sufficient).is_ok());
880
881 let mut new_tx_insufficient = create_legacy_transaction_data();
882 new_tx_insufficient.gas_price = Some(21_000_000_000);
883 assert!(validate_price_bump_requirements(&old_tx, &new_tx_insufficient).is_err());
884 }
885
886 #[test]
887 fn test_validate_price_bump_requirements_eip1559() {
888 let old_tx = create_eip1559_transaction_data();
889
890 let mut new_tx_sufficient = create_eip1559_transaction_data();
891 new_tx_sufficient.max_fee_per_gas = Some(33_000_000_000);
892 new_tx_sufficient.max_priority_fee_per_gas = Some(3_000_000_000);
893 assert!(validate_price_bump_requirements(&old_tx, &new_tx_sufficient).is_ok());
894
895 let mut new_tx_insufficient_max = create_eip1559_transaction_data();
896 new_tx_insufficient_max.max_fee_per_gas = Some(32_000_000_000);
897 new_tx_insufficient_max.max_priority_fee_per_gas = Some(3_000_000_000);
898 assert!(validate_price_bump_requirements(&old_tx, &new_tx_insufficient_max).is_err());
899
900 let mut new_tx_insufficient_priority = create_eip1559_transaction_data();
901 new_tx_insufficient_priority.max_fee_per_gas = Some(33_000_000_000);
902 new_tx_insufficient_priority.max_priority_fee_per_gas = Some(2_100_000_000);
903 assert!(validate_price_bump_requirements(&old_tx, &new_tx_insufficient_priority).is_err());
904 }
905
906 #[test]
907 fn test_validate_price_bump_requirements_partial_eip1559() {
908 let mut old_tx = create_eip1559_transaction_data();
909 old_tx.max_fee_per_gas = Some(30_000_000_000);
910 old_tx.max_priority_fee_per_gas = Some(5_000_000_000);
911
912 let mut new_tx_only_priority = create_legacy_transaction_data();
913 new_tx_only_priority.gas_price = None;
914 new_tx_only_priority.max_fee_per_gas = None;
915 new_tx_only_priority.max_priority_fee_per_gas = Some(6_000_000_000);
916 let result = validate_price_bump_requirements(&old_tx, &new_tx_only_priority);
917 assert!(result.is_err());
918
919 let mut new_tx_only_max = create_legacy_transaction_data();
920 new_tx_only_max.gas_price = None;
921 new_tx_only_max.max_fee_per_gas = Some(33_000_000_000);
922 new_tx_only_max.max_priority_fee_per_gas = None;
923 let result = validate_price_bump_requirements(&old_tx, &new_tx_only_max);
924 assert!(result.is_err());
925
926 let new_legacy = create_legacy_transaction_data();
927 let result = validate_price_bump_requirements(&old_tx, &new_legacy);
928 assert!(result.is_err());
929
930 let old_legacy = create_legacy_transaction_data();
931 let result = validate_price_bump_requirements(&old_legacy, &new_tx_only_priority);
932 assert!(result.is_err());
933 }
934
935 #[test]
936 fn test_validate_price_bump_requirements_missing_pricing_data() {
937 let mut old_tx_no_price = create_legacy_transaction_data();
938 old_tx_no_price.gas_price = None;
939 old_tx_no_price.max_fee_per_gas = None;
940 old_tx_no_price.max_priority_fee_per_gas = None;
941
942 let mut new_tx_no_price = create_legacy_transaction_data();
943 new_tx_no_price.gas_price = None;
944 new_tx_no_price.max_fee_per_gas = None;
945 new_tx_no_price.max_priority_fee_per_gas = None;
946
947 let result = validate_price_bump_requirements(&old_tx_no_price, &new_tx_no_price);
948 assert!(result.is_err()); let new_legacy = create_legacy_transaction_data();
952 let result = validate_price_bump_requirements(&old_tx_no_price, &new_legacy);
953 assert!(result.is_ok());
954
955 let new_eip1559 = create_eip1559_transaction_data();
957 let result = validate_price_bump_requirements(&old_tx_no_price, &new_eip1559);
958 assert!(result.is_ok());
959
960 let old_legacy = create_legacy_transaction_data();
962 let result = validate_price_bump_requirements(&old_legacy, &new_tx_no_price);
963 assert!(result.is_err()); }
965
966 #[test]
967 fn test_validate_explicit_price_bump_zero_gas_price_cap() {
968 let old_tx = create_legacy_transaction_data();
969 let relayer = create_relayer_with_gas_cap(0);
970 let mut new_tx = create_legacy_transaction_data();
971 new_tx.gas_price = Some(1);
972
973 let result = validate_explicit_price_bump(&old_tx, &new_tx, &relayer, false);
974 assert!(result.is_err());
975 }
976
977 #[tokio::test]
978 async fn test_calculate_replacement_price_legacy_missing_old_gas_price() {
979 let mut old_tx = create_legacy_transaction_data();
980 old_tx.gas_price = None;
981 let new_tx = create_legacy_transaction_data();
982 let relayer = create_test_relayer();
983
984 let price_calculator = MockPriceCalculator {
985 gas_price: Some(25_000_000_000),
986 max_fee_per_gas: None,
987 max_priority_fee_per_gas: None,
988 should_error: false,
989 };
990
991 let result =
992 calculate_replacement_price(&old_tx, &new_tx, &relayer, &price_calculator, false).await;
993 assert!(result.is_err());
994 }
995
996 #[tokio::test]
997 async fn test_calculate_replacement_price_eip1559_missing_old_fees() {
998 let mut old_tx = create_eip1559_transaction_data();
999 old_tx.max_fee_per_gas = None;
1000 old_tx.max_priority_fee_per_gas = None;
1001 let new_tx = create_eip1559_transaction_data();
1002 let relayer = create_test_relayer();
1003
1004 let price_calculator = MockPriceCalculator {
1005 gas_price: None,
1006 max_fee_per_gas: Some(40_000_000_000),
1007 max_priority_fee_per_gas: Some(3_000_000_000),
1008 should_error: false,
1009 };
1010
1011 let result =
1012 calculate_replacement_price(&old_tx, &new_tx, &relayer, &price_calculator, false).await;
1013 assert!(result.is_err());
1014 }
1015
1016 #[tokio::test]
1017 async fn test_calculate_replacement_price_force_legacy_with_eip1559_policy_disabled() {
1018 let old_tx = create_eip1559_transaction_data();
1019 let new_tx = create_eip1559_transaction_data();
1020 let mut relayer = create_test_relayer();
1021 if let crate::models::RelayerNetworkPolicy::Evm(ref mut policy) = relayer.policies {
1022 policy.eip1559_pricing = Some(false);
1023 }
1024
1025 let price_calculator = MockPriceCalculator {
1026 gas_price: Some(25_000_000_000),
1027 max_fee_per_gas: None,
1028 max_priority_fee_per_gas: None,
1029 should_error: false,
1030 };
1031
1032 let result =
1033 calculate_replacement_price(&old_tx, &new_tx, &relayer, &price_calculator, false).await;
1034 assert!(result.is_err());
1035 }
1036}