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