1use crate::{
4 identifier::{SenderIdentifiers, TransactionId},
5 pool::txpool::TxPool,
6 traits::TransactionOrigin,
7 CoinbaseTipOrdering, EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction,
8 ValidPoolTransaction,
9};
10use alloy_consensus::{
11 constants::{
12 EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
13 LEGACY_TX_TYPE_ID,
14 },
15 EthereumTxEnvelope, Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702,
16 TxLegacy, TxType, Typed2718,
17};
18use alloy_eips::{
19 eip1559::MIN_PROTOCOL_BASE_FEE,
20 eip2718::Encodable2718,
21 eip2930::AccessList,
22 eip4844::{BlobTransactionSidecar, BlobTransactionValidationError, DATA_GAS_PER_BLOB},
23 eip7594::BlobTransactionSidecarVariant,
24 eip7702::SignedAuthorization,
25};
26use alloy_primitives::{Address, Bytes, ChainId, Signature, TxHash, TxKind, B256, U256};
27use paste::paste;
28use rand::{distr::Uniform, prelude::Distribution};
29use reth_ethereum_primitives::{PooledTransactionVariant, Transaction, TransactionSigned};
30use reth_primitives_traits::{
31 transaction::error::TryFromRecoveredTransactionError, InMemorySize, Recovered,
32 SignedTransaction,
33};
34
35use alloy_consensus::error::ValueError;
36use alloy_eips::eip4844::env_settings::KzgSettings;
37use rand::distr::weighted::WeightedIndex;
38use std::{ops::Range, sync::Arc, time::Instant, vec::IntoIter};
39
40pub type MockTxPool = TxPool<MockOrdering>;
44
45pub type MockValidTx = ValidPoolTransaction<MockTransaction>;
50
51pub fn mock_tx_pool() -> MockTxPool {
53 MockTxPool::new(Default::default(), Default::default())
54}
55
56macro_rules! set_value {
58 (&mut $this:expr => $field:ident) => {{
60 let new_value = $field;
61 match $this {
62 MockTransaction::Legacy { $field, .. } => {
63 *$field = new_value;
64 }
65 MockTransaction::Eip1559 { $field, .. } => {
66 *$field = new_value;
67 }
68 MockTransaction::Eip4844 { $field, .. } => {
69 *$field = new_value;
70 }
71 MockTransaction::Eip2930 { $field, .. } => {
72 *$field = new_value;
73 }
74 MockTransaction::Eip7702 { $field, .. } => {
75 *$field = new_value;
76 }
77 }
78 $this.update_cost();
80 }};
81 ($this:expr => $field:ident) => {{
83 let new_value = $field;
84 match $this {
85 MockTransaction::Legacy { ref mut $field, .. } |
86 MockTransaction::Eip1559 { ref mut $field, .. } |
87 MockTransaction::Eip4844 { ref mut $field, .. } |
88 MockTransaction::Eip2930 { ref mut $field, .. } |
89 MockTransaction::Eip7702 { ref mut $field, .. } => {
90 *$field = new_value;
91 }
92 }
93 $this.update_cost();
95 }};
96}
97
98macro_rules! get_value {
100 ($this:tt => $field:ident) => {
101 match $this {
102 MockTransaction::Legacy { $field, .. } |
103 MockTransaction::Eip1559 { $field, .. } |
104 MockTransaction::Eip4844 { $field, .. } |
105 MockTransaction::Eip2930 { $field, .. } |
106 MockTransaction::Eip7702 { $field, .. } => $field,
107 }
108 };
109}
110
111macro_rules! make_setters_getters {
113 ($($name:ident => $t:ty);*) => {
114 paste! {$(
115 pub fn [<set_ $name>](&mut self, $name: $t) -> &mut Self {
117 set_value!(&mut self => $name);
118 self
119 }
120
121 pub fn [<with_ $name>](mut self, $name: $t) -> Self {
123 set_value!(self => $name);
124 self
125 }
126
127 pub const fn [<get_ $name>](&self) -> &$t {
129 get_value!(self => $name)
130 }
131 )*}
132 };
133}
134
135#[derive(Debug, Clone, Eq, PartialEq)]
137pub enum MockTransaction {
138 Legacy {
140 chain_id: Option<ChainId>,
142 hash: B256,
144 sender: Address,
146 nonce: u64,
148 gas_price: u128,
150 gas_limit: u64,
152 to: TxKind,
154 value: U256,
156 input: Bytes,
158 size: usize,
160 cost: U256,
162 },
163 Eip2930 {
165 chain_id: ChainId,
167 hash: B256,
169 sender: Address,
171 nonce: u64,
173 to: TxKind,
175 gas_limit: u64,
177 input: Bytes,
179 value: U256,
181 gas_price: u128,
183 access_list: AccessList,
185 size: usize,
187 cost: U256,
189 },
190 Eip1559 {
192 chain_id: ChainId,
194 hash: B256,
196 sender: Address,
198 nonce: u64,
200 max_fee_per_gas: u128,
202 max_priority_fee_per_gas: u128,
204 gas_limit: u64,
206 to: TxKind,
208 value: U256,
210 access_list: AccessList,
212 input: Bytes,
214 size: usize,
216 cost: U256,
218 },
219 Eip4844 {
221 chain_id: ChainId,
223 hash: B256,
225 sender: Address,
227 nonce: u64,
229 max_fee_per_gas: u128,
231 max_priority_fee_per_gas: u128,
233 max_fee_per_blob_gas: u128,
235 gas_limit: u64,
237 to: Address,
239 value: U256,
241 access_list: AccessList,
243 input: Bytes,
245 sidecar: BlobTransactionSidecarVariant,
247 blob_versioned_hashes: Vec<B256>,
249 size: usize,
251 cost: U256,
253 },
254 Eip7702 {
256 chain_id: ChainId,
258 hash: B256,
260 sender: Address,
262 nonce: u64,
264 max_fee_per_gas: u128,
266 max_priority_fee_per_gas: u128,
268 gas_limit: u64,
270 to: Address,
272 value: U256,
274 access_list: AccessList,
276 authorization_list: Vec<SignedAuthorization>,
278 input: Bytes,
280 size: usize,
282 cost: U256,
284 },
285}
286
287impl MockTransaction {
290 make_setters_getters! {
291 nonce => u64;
292 hash => B256;
293 sender => Address;
294 gas_limit => u64;
295 value => U256;
296 input => Bytes;
297 size => usize
298 }
299
300 pub fn legacy() -> Self {
302 Self::Legacy {
303 chain_id: Some(1),
304 hash: B256::random(),
305 sender: Address::random(),
306 nonce: 0,
307 gas_price: 0,
308 gas_limit: 0,
309 to: Address::random().into(),
310 value: Default::default(),
311 input: Default::default(),
312 size: Default::default(),
313 cost: U256::ZERO,
314 }
315 }
316
317 pub fn eip2930() -> Self {
319 Self::Eip2930 {
320 chain_id: 1,
321 hash: B256::random(),
322 sender: Address::random(),
323 nonce: 0,
324 to: Address::random().into(),
325 gas_limit: 0,
326 input: Bytes::new(),
327 value: Default::default(),
328 gas_price: 0,
329 access_list: Default::default(),
330 size: Default::default(),
331 cost: U256::ZERO,
332 }
333 }
334
335 pub fn eip1559() -> Self {
337 Self::Eip1559 {
338 chain_id: 1,
339 hash: B256::random(),
340 sender: Address::random(),
341 nonce: 0,
342 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
343 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
344 gas_limit: 0,
345 to: Address::random().into(),
346 value: Default::default(),
347 input: Bytes::new(),
348 access_list: Default::default(),
349 size: Default::default(),
350 cost: U256::ZERO,
351 }
352 }
353
354 pub fn eip7702() -> Self {
356 Self::Eip7702 {
357 chain_id: 1,
358 hash: B256::random(),
359 sender: Address::random(),
360 nonce: 0,
361 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
362 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
363 gas_limit: 0,
364 to: Address::random(),
365 value: Default::default(),
366 input: Bytes::new(),
367 access_list: Default::default(),
368 authorization_list: vec![],
369 size: Default::default(),
370 cost: U256::ZERO,
371 }
372 }
373
374 pub fn eip4844() -> Self {
376 Self::Eip4844 {
377 chain_id: 1,
378 hash: B256::random(),
379 sender: Address::random(),
380 nonce: 0,
381 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
382 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
383 max_fee_per_blob_gas: DATA_GAS_PER_BLOB as u128,
384 gas_limit: 0,
385 to: Address::random(),
386 value: Default::default(),
387 input: Bytes::new(),
388 access_list: Default::default(),
389 sidecar: BlobTransactionSidecarVariant::Eip4844(Default::default()),
390 blob_versioned_hashes: Default::default(),
391 size: Default::default(),
392 cost: U256::ZERO,
393 }
394 }
395
396 pub fn eip4844_with_sidecar(sidecar: BlobTransactionSidecarVariant) -> Self {
398 let mut transaction = Self::eip4844();
399 if let Self::Eip4844 { sidecar: existing_sidecar, blob_versioned_hashes, .. } =
400 &mut transaction
401 {
402 *blob_versioned_hashes = sidecar.versioned_hashes().collect();
403 *existing_sidecar = sidecar;
404 }
405 transaction
406 }
407
408 pub fn new_from_type(tx_type: TxType) -> Self {
417 match tx_type {
418 TxType::Legacy => Self::legacy(),
419 TxType::Eip2930 => Self::eip2930(),
420 TxType::Eip1559 => Self::eip1559(),
421 TxType::Eip4844 => Self::eip4844(),
422 TxType::Eip7702 => Self::eip7702(),
423 }
424 }
425
426 pub const fn with_blob_fee(mut self, val: u128) -> Self {
428 self.set_blob_fee(val);
429 self
430 }
431
432 pub fn with_blob_hashes(mut self, count: usize) -> Self {
434 if let Self::Eip4844 { blob_versioned_hashes, .. } = &mut self {
435 *blob_versioned_hashes = (0..count).map(|_| B256::random()).collect();
436 }
437 self
438 }
439
440 pub const fn set_blob_fee(&mut self, val: u128) -> &mut Self {
442 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = self {
443 *max_fee_per_blob_gas = val;
444 }
445 self
446 }
447
448 pub const fn set_priority_fee(&mut self, val: u128) -> &mut Self {
450 if let Self::Eip1559 { max_priority_fee_per_gas, .. } |
451 Self::Eip4844 { max_priority_fee_per_gas, .. } = self
452 {
453 *max_priority_fee_per_gas = val;
454 }
455 self
456 }
457
458 pub const fn with_priority_fee(mut self, val: u128) -> Self {
460 self.set_priority_fee(val);
461 self
462 }
463
464 pub const fn get_priority_fee(&self) -> Option<u128> {
466 match self {
467 Self::Eip1559 { max_priority_fee_per_gas, .. } |
468 Self::Eip4844 { max_priority_fee_per_gas, .. } |
469 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
470 _ => None,
471 }
472 }
473
474 pub const fn set_max_fee(&mut self, val: u128) -> &mut Self {
476 if let Self::Eip1559 { max_fee_per_gas, .. } |
477 Self::Eip4844 { max_fee_per_gas, .. } |
478 Self::Eip7702 { max_fee_per_gas, .. } = self
479 {
480 *max_fee_per_gas = val;
481 }
482 self
483 }
484
485 pub const fn with_max_fee(mut self, val: u128) -> Self {
487 self.set_max_fee(val);
488 self
489 }
490
491 pub const fn get_max_fee(&self) -> Option<u128> {
493 match self {
494 Self::Eip1559 { max_fee_per_gas, .. } |
495 Self::Eip4844 { max_fee_per_gas, .. } |
496 Self::Eip7702 { max_fee_per_gas, .. } => Some(*max_fee_per_gas),
497 _ => None,
498 }
499 }
500
501 pub fn set_accesslist(&mut self, list: AccessList) -> &mut Self {
503 match self {
504 Self::Legacy { .. } => {}
505 Self::Eip1559 { access_list: accesslist, .. } |
506 Self::Eip4844 { access_list: accesslist, .. } |
507 Self::Eip2930 { access_list: accesslist, .. } |
508 Self::Eip7702 { access_list: accesslist, .. } => {
509 *accesslist = list;
510 }
511 }
512 self
513 }
514
515 pub fn set_authorization_list(&mut self, list: Vec<SignedAuthorization>) -> &mut Self {
517 if let Self::Eip7702 { authorization_list, .. } = self {
518 *authorization_list = list;
519 }
520
521 self
522 }
523
524 pub const fn set_gas_price(&mut self, val: u128) -> &mut Self {
526 match self {
527 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => {
528 *gas_price = val;
529 }
530 Self::Eip1559 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
531 Self::Eip4844 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
532 Self::Eip7702 { max_fee_per_gas, max_priority_fee_per_gas, .. } => {
533 *max_fee_per_gas = val;
534 *max_priority_fee_per_gas = val;
535 }
536 }
537 self
538 }
539
540 pub const fn with_gas_price(mut self, val: u128) -> Self {
542 match self {
543 Self::Legacy { ref mut gas_price, .. } | Self::Eip2930 { ref mut gas_price, .. } => {
544 *gas_price = val;
545 }
546 Self::Eip1559 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
547 Self::Eip4844 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
548 Self::Eip7702 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } => {
549 *max_fee_per_gas = val;
550 *max_priority_fee_per_gas = val;
551 }
552 }
553 self
554 }
555
556 pub const fn get_gas_price(&self) -> u128 {
558 match self {
559 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
560 Self::Eip1559 { max_fee_per_gas, .. } |
561 Self::Eip4844 { max_fee_per_gas, .. } |
562 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
563 }
564 }
565
566 pub fn prev(&self) -> Self {
568 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() - 1)
569 }
570
571 pub fn next(&self) -> Self {
573 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + 1)
574 }
575
576 pub fn skip(&self, skip: u64) -> Self {
578 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + skip + 1)
579 }
580
581 pub fn inc_nonce(self) -> Self {
583 let nonce = self.get_nonce() + 1;
584 self.with_nonce(nonce)
585 }
586
587 pub fn rng_hash(self) -> Self {
589 self.with_hash(B256::random())
590 }
591
592 pub fn inc_price(&self) -> Self {
594 self.inc_price_by(1)
595 }
596
597 pub fn inc_price_by(&self, value: u128) -> Self {
599 self.clone().with_gas_price(self.get_gas_price().checked_add(value).unwrap())
600 }
601
602 pub fn decr_price(&self) -> Self {
604 self.decr_price_by(1)
605 }
606
607 pub fn decr_price_by(&self, value: u128) -> Self {
609 self.clone().with_gas_price(self.get_gas_price().checked_sub(value).unwrap())
610 }
611
612 pub fn inc_value(&self) -> Self {
614 self.clone().with_value(self.get_value().checked_add(U256::from(1)).unwrap())
615 }
616
617 pub fn inc_limit(&self) -> Self {
619 self.clone().with_gas_limit(self.get_gas_limit() + 1)
620 }
621
622 pub fn inc_blob_fee(&self) -> Self {
626 self.inc_blob_fee_by(1)
627 }
628
629 pub fn inc_blob_fee_by(&self, value: u128) -> Self {
633 let mut this = self.clone();
634 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
635 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_add(value).unwrap();
636 }
637 this
638 }
639
640 pub fn decr_blob_fee(&self) -> Self {
644 self.decr_price_by(1)
645 }
646
647 pub fn decr_blob_fee_by(&self, value: u128) -> Self {
651 let mut this = self.clone();
652 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
653 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_sub(value).unwrap();
654 }
655 this
656 }
657
658 pub const fn tx_type(&self) -> u8 {
660 match self {
661 Self::Legacy { .. } => LEGACY_TX_TYPE_ID,
662 Self::Eip1559 { .. } => EIP1559_TX_TYPE_ID,
663 Self::Eip4844 { .. } => EIP4844_TX_TYPE_ID,
664 Self::Eip2930 { .. } => EIP2930_TX_TYPE_ID,
665 Self::Eip7702 { .. } => EIP7702_TX_TYPE_ID,
666 }
667 }
668
669 pub const fn is_legacy(&self) -> bool {
671 matches!(self, Self::Legacy { .. })
672 }
673
674 pub const fn is_eip1559(&self) -> bool {
676 matches!(self, Self::Eip1559 { .. })
677 }
678
679 pub const fn is_eip4844(&self) -> bool {
681 matches!(self, Self::Eip4844 { .. })
682 }
683
684 pub const fn is_eip2930(&self) -> bool {
686 matches!(self, Self::Eip2930 { .. })
687 }
688
689 pub const fn is_eip7702(&self) -> bool {
691 matches!(self, Self::Eip7702 { .. })
692 }
693
694 fn update_cost(&mut self) {
695 match self {
696 Self::Legacy { cost, gas_limit, gas_price, value, .. } |
697 Self::Eip2930 { cost, gas_limit, gas_price, value, .. } => {
698 *cost = U256::from(*gas_limit) * U256::from(*gas_price) + *value
699 }
700 Self::Eip1559 { cost, gas_limit, max_fee_per_gas, value, .. } |
701 Self::Eip4844 { cost, gas_limit, max_fee_per_gas, value, .. } |
702 Self::Eip7702 { cost, gas_limit, max_fee_per_gas, value, .. } => {
703 *cost = U256::from(*gas_limit) * U256::from(*max_fee_per_gas) + *value
704 }
705 };
706 }
707}
708
709impl PoolTransaction for MockTransaction {
710 type TryFromConsensusError = ValueError<EthereumTxEnvelope<TxEip4844>>;
711
712 type Consensus = TransactionSigned;
713
714 type Pooled = PooledTransactionVariant;
715
716 fn encoded_2718_consensus(&self) -> Bytes {
717 self.clone_into_consensus().encoded_2718().into()
718 }
719
720 fn consensus_ref(&self) -> Recovered<&Self::Consensus> {
721 unimplemented!("mock transaction does not wrap a consensus transaction")
722 }
723
724 fn into_consensus(self) -> Recovered<Self::Consensus> {
725 self.into()
726 }
727
728 fn from_pooled(pooled: Recovered<Self::Pooled>) -> Self {
729 pooled.into()
730 }
731
732 fn hash(&self) -> &TxHash {
733 self.get_hash()
734 }
735
736 fn sender(&self) -> Address {
737 *self.get_sender()
738 }
739
740 fn sender_ref(&self) -> &Address {
741 self.get_sender()
742 }
743
744 fn cost(&self) -> &U256 {
749 match self {
750 Self::Legacy { cost, .. } |
751 Self::Eip2930 { cost, .. } |
752 Self::Eip1559 { cost, .. } |
753 Self::Eip4844 { cost, .. } |
754 Self::Eip7702 { cost, .. } => cost,
755 }
756 }
757
758 fn encoded_length(&self) -> usize {
760 self.size()
761 }
762}
763
764impl InMemorySize for MockTransaction {
765 fn size(&self) -> usize {
766 *self.get_size()
767 }
768}
769
770impl Typed2718 for MockTransaction {
771 fn ty(&self) -> u8 {
772 match self {
773 Self::Legacy { .. } => TxType::Legacy.into(),
774 Self::Eip1559 { .. } => TxType::Eip1559.into(),
775 Self::Eip4844 { .. } => TxType::Eip4844.into(),
776 Self::Eip2930 { .. } => TxType::Eip2930.into(),
777 Self::Eip7702 { .. } => TxType::Eip7702.into(),
778 }
779 }
780}
781
782impl alloy_consensus::Transaction for MockTransaction {
783 fn chain_id(&self) -> Option<u64> {
784 match self {
785 Self::Legacy { chain_id, .. } => *chain_id,
786 Self::Eip1559 { chain_id, .. } |
787 Self::Eip4844 { chain_id, .. } |
788 Self::Eip2930 { chain_id, .. } |
789 Self::Eip7702 { chain_id, .. } => Some(*chain_id),
790 }
791 }
792
793 fn nonce(&self) -> u64 {
794 *self.get_nonce()
795 }
796
797 fn gas_limit(&self) -> u64 {
798 *self.get_gas_limit()
799 }
800
801 fn gas_price(&self) -> Option<u128> {
802 match self {
803 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => Some(*gas_price),
804 _ => None,
805 }
806 }
807
808 fn max_fee_per_gas(&self) -> u128 {
809 match self {
810 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
811 Self::Eip1559 { max_fee_per_gas, .. } |
812 Self::Eip4844 { max_fee_per_gas, .. } |
813 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
814 }
815 }
816
817 fn max_priority_fee_per_gas(&self) -> Option<u128> {
818 match self {
819 Self::Legacy { .. } | Self::Eip2930 { .. } => None,
820 Self::Eip1559 { max_priority_fee_per_gas, .. } |
821 Self::Eip4844 { max_priority_fee_per_gas, .. } |
822 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
823 }
824 }
825
826 fn max_fee_per_blob_gas(&self) -> Option<u128> {
827 match self {
828 Self::Eip4844 { max_fee_per_blob_gas, .. } => Some(*max_fee_per_blob_gas),
829 _ => None,
830 }
831 }
832
833 fn priority_fee_or_price(&self) -> u128 {
834 match self {
835 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
836 Self::Eip1559 { max_priority_fee_per_gas, .. } |
837 Self::Eip4844 { max_priority_fee_per_gas, .. } |
838 Self::Eip7702 { max_priority_fee_per_gas, .. } => *max_priority_fee_per_gas,
839 }
840 }
841
842 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
843 base_fee.map_or_else(
844 || self.max_fee_per_gas(),
845 |base_fee| {
846 let tip = self.max_fee_per_gas().saturating_sub(base_fee as u128);
849 if let Some(max_tip) = self.max_priority_fee_per_gas() {
850 if tip > max_tip {
851 max_tip + base_fee as u128
852 } else {
853 self.max_fee_per_gas()
855 }
856 } else {
857 self.max_fee_per_gas()
858 }
859 },
860 )
861 }
862
863 fn is_dynamic_fee(&self) -> bool {
864 !matches!(self, Self::Legacy { .. } | Self::Eip2930 { .. })
865 }
866
867 fn kind(&self) -> TxKind {
868 match self {
869 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => *to,
870 Self::Eip4844 { to, .. } | Self::Eip7702 { to, .. } => TxKind::Call(*to),
871 }
872 }
873
874 fn is_create(&self) -> bool {
875 match self {
876 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => {
877 to.is_create()
878 }
879 Self::Eip4844 { .. } | Self::Eip7702 { .. } => false,
880 }
881 }
882
883 fn value(&self) -> U256 {
884 match self {
885 Self::Legacy { value, .. } |
886 Self::Eip1559 { value, .. } |
887 Self::Eip2930 { value, .. } |
888 Self::Eip4844 { value, .. } |
889 Self::Eip7702 { value, .. } => *value,
890 }
891 }
892
893 fn input(&self) -> &Bytes {
894 self.get_input()
895 }
896
897 fn access_list(&self) -> Option<&AccessList> {
898 match self {
899 Self::Legacy { .. } => None,
900 Self::Eip1559 { access_list: accesslist, .. } |
901 Self::Eip4844 { access_list: accesslist, .. } |
902 Self::Eip2930 { access_list: accesslist, .. } |
903 Self::Eip7702 { access_list: accesslist, .. } => Some(accesslist),
904 }
905 }
906
907 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
908 match self {
909 Self::Eip4844 { blob_versioned_hashes, .. } => Some(blob_versioned_hashes),
910 _ => None,
911 }
912 }
913
914 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
915 match self {
916 Self::Eip7702 { authorization_list, .. } => Some(authorization_list),
917 _ => None,
918 }
919 }
920}
921
922impl EthPoolTransaction for MockTransaction {
923 fn take_blob(&mut self) -> EthBlobTransactionSidecar {
924 match self {
925 Self::Eip4844 { sidecar, .. } => {
926 EthBlobTransactionSidecar::Present(sidecar.clone().into())
927 }
928 _ => EthBlobTransactionSidecar::None,
929 }
930 }
931
932 fn try_into_pooled_eip4844(
933 self,
934 sidecar: Arc<BlobTransactionSidecarVariant>,
935 ) -> Option<Recovered<Self::Pooled>> {
936 let (tx, signer) = self.into_consensus().into_parts();
937 tx.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar))
938 .map(|tx| tx.with_signer(signer))
939 .ok()
940 }
941
942 fn try_from_eip4844(
943 tx: Recovered<Self::Consensus>,
944 sidecar: BlobTransactionSidecarVariant,
945 ) -> Option<Self> {
946 let (tx, signer) = tx.into_parts();
947 tx.try_into_pooled_eip4844(sidecar)
948 .map(|tx| tx.with_signer(signer))
949 .ok()
950 .map(Self::from_pooled)
951 }
952
953 fn validate_blob(
954 &self,
955 _blob: &BlobTransactionSidecarVariant,
956 _settings: &KzgSettings,
957 ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> {
958 match &self {
959 Self::Eip4844 { .. } => Ok(()),
960 _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
961 }
962 }
963}
964
965impl TryFrom<Recovered<TransactionSigned>> for MockTransaction {
966 type Error = TryFromRecoveredTransactionError;
967
968 fn try_from(tx: Recovered<TransactionSigned>) -> Result<Self, Self::Error> {
969 let sender = tx.signer();
970 let transaction = tx.into_inner();
971 let hash = *transaction.tx_hash();
972 let size = transaction.size();
973
974 match transaction.into_typed_transaction() {
975 Transaction::Legacy(TxLegacy {
976 chain_id,
977 nonce,
978 gas_price,
979 gas_limit,
980 to,
981 value,
982 input,
983 }) => Ok(Self::Legacy {
984 chain_id,
985 hash,
986 sender,
987 nonce,
988 gas_price,
989 gas_limit,
990 to,
991 value,
992 input,
993 size,
994 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
995 }),
996 Transaction::Eip2930(TxEip2930 {
997 chain_id,
998 nonce,
999 gas_price,
1000 gas_limit,
1001 to,
1002 value,
1003 input,
1004 access_list,
1005 }) => Ok(Self::Eip2930 {
1006 chain_id,
1007 hash,
1008 sender,
1009 nonce,
1010 gas_price,
1011 gas_limit,
1012 to,
1013 value,
1014 input,
1015 access_list,
1016 size,
1017 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
1018 }),
1019 Transaction::Eip1559(TxEip1559 {
1020 chain_id,
1021 nonce,
1022 gas_limit,
1023 max_fee_per_gas,
1024 max_priority_fee_per_gas,
1025 to,
1026 value,
1027 input,
1028 access_list,
1029 }) => Ok(Self::Eip1559 {
1030 chain_id,
1031 hash,
1032 sender,
1033 nonce,
1034 max_fee_per_gas,
1035 max_priority_fee_per_gas,
1036 gas_limit,
1037 to,
1038 value,
1039 input,
1040 access_list,
1041 size,
1042 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1043 }),
1044 Transaction::Eip4844(TxEip4844 {
1045 chain_id,
1046 nonce,
1047 gas_limit,
1048 max_fee_per_gas,
1049 max_priority_fee_per_gas,
1050 to,
1051 value,
1052 input,
1053 access_list,
1054 blob_versioned_hashes: _,
1055 max_fee_per_blob_gas,
1056 }) => Ok(Self::Eip4844 {
1057 chain_id,
1058 hash,
1059 sender,
1060 nonce,
1061 max_fee_per_gas,
1062 max_priority_fee_per_gas,
1063 max_fee_per_blob_gas,
1064 gas_limit,
1065 to,
1066 value,
1067 input,
1068 access_list,
1069 sidecar: BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default()),
1070 blob_versioned_hashes: Default::default(),
1071 size,
1072 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1073 }),
1074 Transaction::Eip7702(TxEip7702 {
1075 chain_id,
1076 nonce,
1077 gas_limit,
1078 max_fee_per_gas,
1079 max_priority_fee_per_gas,
1080 to,
1081 value,
1082 access_list,
1083 authorization_list,
1084 input,
1085 }) => Ok(Self::Eip7702 {
1086 chain_id,
1087 hash,
1088 sender,
1089 nonce,
1090 max_fee_per_gas,
1091 max_priority_fee_per_gas,
1092 gas_limit,
1093 to,
1094 value,
1095 input,
1096 access_list,
1097 authorization_list,
1098 size,
1099 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1100 }),
1101 }
1102 }
1103}
1104
1105impl TryFrom<Recovered<EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>>>
1106 for MockTransaction
1107{
1108 type Error = TryFromRecoveredTransactionError;
1109
1110 fn try_from(
1111 tx: Recovered<EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>>,
1112 ) -> Result<Self, Self::Error> {
1113 let sender = tx.signer();
1114 let transaction = tx.into_inner();
1115 let hash = *transaction.tx_hash();
1116 let size = transaction.size();
1117
1118 match transaction {
1119 EthereumTxEnvelope::Legacy(signed_tx) => {
1120 let tx = signed_tx.strip_signature();
1121 Ok(Self::Legacy {
1122 chain_id: tx.chain_id,
1123 hash,
1124 sender,
1125 nonce: tx.nonce,
1126 gas_price: tx.gas_price,
1127 gas_limit: tx.gas_limit,
1128 to: tx.to,
1129 value: tx.value,
1130 input: tx.input,
1131 size,
1132 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1133 })
1134 }
1135 EthereumTxEnvelope::Eip2930(signed_tx) => {
1136 let tx = signed_tx.strip_signature();
1137 Ok(Self::Eip2930 {
1138 chain_id: tx.chain_id,
1139 hash,
1140 sender,
1141 nonce: tx.nonce,
1142 gas_price: tx.gas_price,
1143 gas_limit: tx.gas_limit,
1144 to: tx.to,
1145 value: tx.value,
1146 input: tx.input,
1147 access_list: tx.access_list,
1148 size,
1149 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1150 })
1151 }
1152 EthereumTxEnvelope::Eip1559(signed_tx) => {
1153 let tx = signed_tx.strip_signature();
1154 Ok(Self::Eip1559 {
1155 chain_id: tx.chain_id,
1156 hash,
1157 sender,
1158 nonce: tx.nonce,
1159 max_fee_per_gas: tx.max_fee_per_gas,
1160 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1161 gas_limit: tx.gas_limit,
1162 to: tx.to,
1163 value: tx.value,
1164 input: tx.input,
1165 access_list: tx.access_list,
1166 size,
1167 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1168 })
1169 }
1170 EthereumTxEnvelope::Eip4844(signed_tx) => match signed_tx.tx() {
1171 TxEip4844Variant::TxEip4844(tx) => Ok(Self::Eip4844 {
1172 chain_id: tx.chain_id,
1173 hash,
1174 sender,
1175 nonce: tx.nonce,
1176 max_fee_per_gas: tx.max_fee_per_gas,
1177 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1178 max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
1179 gas_limit: tx.gas_limit,
1180 to: tx.to,
1181 value: tx.value,
1182 input: tx.input.clone(),
1183 access_list: tx.access_list.clone(),
1184 sidecar: BlobTransactionSidecarVariant::Eip4844(
1185 BlobTransactionSidecar::default(),
1186 ),
1187 blob_versioned_hashes: tx.blob_versioned_hashes.clone(),
1188 size,
1189 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1190 }),
1191 tx => Err(TryFromRecoveredTransactionError::UnsupportedTransactionType(tx.ty())),
1192 },
1193 EthereumTxEnvelope::Eip7702(signed_tx) => {
1194 let tx = signed_tx.strip_signature();
1195 Ok(Self::Eip7702 {
1196 chain_id: tx.chain_id,
1197 hash,
1198 sender,
1199 nonce: tx.nonce,
1200 max_fee_per_gas: tx.max_fee_per_gas,
1201 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1202 gas_limit: tx.gas_limit,
1203 to: tx.to,
1204 value: tx.value,
1205 access_list: tx.access_list,
1206 authorization_list: tx.authorization_list,
1207 input: tx.input,
1208 size,
1209 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1210 })
1211 }
1212 }
1213 }
1214}
1215
1216impl From<Recovered<PooledTransactionVariant>> for MockTransaction {
1217 fn from(tx: Recovered<PooledTransactionVariant>) -> Self {
1218 let (tx, signer) = tx.into_parts();
1219 Recovered::<TransactionSigned>::new_unchecked(tx.into(), signer).try_into().expect(
1220 "Failed to convert from PooledTransactionsElementEcRecovered to MockTransaction",
1221 )
1222 }
1223}
1224
1225impl From<MockTransaction> for Recovered<TransactionSigned> {
1226 fn from(tx: MockTransaction) -> Self {
1227 let hash = *tx.hash();
1228 let sender = tx.sender();
1229 let tx = Transaction::from(tx);
1230 let tx: TransactionSigned =
1231 Signed::new_unchecked(tx, Signature::test_signature(), hash).into();
1232 Self::new_unchecked(tx, sender)
1233 }
1234}
1235
1236impl From<MockTransaction> for Transaction {
1237 fn from(mock: MockTransaction) -> Self {
1238 match mock {
1239 MockTransaction::Legacy {
1240 chain_id,
1241 nonce,
1242 gas_price,
1243 gas_limit,
1244 to,
1245 value,
1246 input,
1247 ..
1248 } => Self::Legacy(TxLegacy { chain_id, nonce, gas_price, gas_limit, to, value, input }),
1249 MockTransaction::Eip2930 {
1250 chain_id,
1251 nonce,
1252 gas_price,
1253 gas_limit,
1254 to,
1255 value,
1256 access_list,
1257 input,
1258 ..
1259 } => Self::Eip2930(TxEip2930 {
1260 chain_id,
1261 nonce,
1262 gas_price,
1263 gas_limit,
1264 to,
1265 value,
1266 access_list,
1267 input,
1268 }),
1269 MockTransaction::Eip1559 {
1270 chain_id,
1271 nonce,
1272 gas_limit,
1273 max_fee_per_gas,
1274 max_priority_fee_per_gas,
1275 to,
1276 value,
1277 access_list,
1278 input,
1279 ..
1280 } => Self::Eip1559(TxEip1559 {
1281 chain_id,
1282 nonce,
1283 gas_limit,
1284 max_fee_per_gas,
1285 max_priority_fee_per_gas,
1286 to,
1287 value,
1288 access_list,
1289 input,
1290 }),
1291 MockTransaction::Eip4844 {
1292 chain_id,
1293 nonce,
1294 gas_limit,
1295 max_fee_per_gas,
1296 max_priority_fee_per_gas,
1297 to,
1298 value,
1299 access_list,
1300 sidecar,
1301 max_fee_per_blob_gas,
1302 input,
1303 ..
1304 } => Self::Eip4844(TxEip4844 {
1305 chain_id,
1306 nonce,
1307 gas_limit,
1308 max_fee_per_gas,
1309 max_priority_fee_per_gas,
1310 to,
1311 value,
1312 access_list,
1313 blob_versioned_hashes: sidecar.versioned_hashes().collect(),
1314 max_fee_per_blob_gas,
1315 input,
1316 }),
1317 MockTransaction::Eip7702 {
1318 chain_id,
1319 nonce,
1320 gas_limit,
1321 max_fee_per_gas,
1322 max_priority_fee_per_gas,
1323 to,
1324 value,
1325 access_list,
1326 input,
1327 authorization_list,
1328 ..
1329 } => Self::Eip7702(TxEip7702 {
1330 chain_id,
1331 nonce,
1332 gas_limit,
1333 max_fee_per_gas,
1334 max_priority_fee_per_gas,
1335 to,
1336 value,
1337 access_list,
1338 authorization_list,
1339 input,
1340 }),
1341 }
1342 }
1343}
1344
1345#[cfg(any(test, feature = "arbitrary"))]
1346impl proptest::arbitrary::Arbitrary for MockTransaction {
1347 type Parameters = ();
1348 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1349 use proptest::prelude::Strategy;
1350 use proptest_arbitrary_interop::arb;
1351
1352 arb::<(TransactionSigned, Address)>()
1353 .prop_map(|(signed_transaction, signer)| {
1354 Recovered::new_unchecked(signed_transaction, signer)
1355 .try_into()
1356 .expect("Failed to create an Arbitrary MockTransaction from a Recovered tx")
1357 })
1358 .boxed()
1359 }
1360
1361 type Strategy = proptest::strategy::BoxedStrategy<Self>;
1362}
1363
1364#[derive(Debug, Default)]
1366pub struct MockTransactionFactory {
1367 pub(crate) ids: SenderIdentifiers,
1368}
1369
1370impl MockTransactionFactory {
1373 pub fn tx_id(&mut self, tx: &MockTransaction) -> TransactionId {
1375 let sender = self.ids.sender_id_or_create(tx.sender());
1376 TransactionId::new(sender, *tx.get_nonce())
1377 }
1378
1379 pub fn validated(&mut self, transaction: MockTransaction) -> MockValidTx {
1381 self.validated_with_origin(TransactionOrigin::External, transaction)
1382 }
1383
1384 pub fn validated_arc(&mut self, transaction: MockTransaction) -> Arc<MockValidTx> {
1386 Arc::new(self.validated(transaction))
1387 }
1388
1389 pub fn validated_with_origin(
1391 &mut self,
1392 origin: TransactionOrigin,
1393 transaction: MockTransaction,
1394 ) -> MockValidTx {
1395 MockValidTx {
1396 propagate: false,
1397 transaction_id: self.tx_id(&transaction),
1398 transaction,
1399 timestamp: Instant::now(),
1400 origin,
1401 authority_ids: None,
1402 }
1403 }
1404
1405 pub fn create_legacy(&mut self) -> MockValidTx {
1407 self.validated(MockTransaction::legacy())
1408 }
1409
1410 pub fn create_eip1559(&mut self) -> MockValidTx {
1412 self.validated(MockTransaction::eip1559())
1413 }
1414
1415 pub fn create_eip4844(&mut self) -> MockValidTx {
1417 self.validated(MockTransaction::eip4844())
1418 }
1419}
1420
1421pub type MockOrdering = CoinbaseTipOrdering<MockTransaction>;
1423
1424#[derive(Debug, Clone)]
1427pub struct MockTransactionRatio {
1428 pub legacy_pct: u32,
1430 pub access_list_pct: u32,
1432 pub dynamic_fee_pct: u32,
1434 pub blob_pct: u32,
1436}
1437
1438impl MockTransactionRatio {
1439 pub fn new(legacy_pct: u32, access_list_pct: u32, dynamic_fee_pct: u32, blob_pct: u32) -> Self {
1445 let total = legacy_pct + access_list_pct + dynamic_fee_pct + blob_pct;
1446 assert_eq!(
1447 total,
1448 100,
1449 "percentages must sum up to 100, instead got legacy: {legacy_pct}, access_list: {access_list_pct}, dynamic_fee: {dynamic_fee_pct}, blob: {blob_pct}, total: {total}",
1450 );
1451
1452 Self { legacy_pct, access_list_pct, dynamic_fee_pct, blob_pct }
1453 }
1454
1455 pub fn weighted_index(&self) -> WeightedIndex<u32> {
1463 WeightedIndex::new([
1464 self.legacy_pct,
1465 self.access_list_pct,
1466 self.dynamic_fee_pct,
1467 self.blob_pct,
1468 ])
1469 .unwrap()
1470 }
1471}
1472
1473#[derive(Debug, Clone)]
1475pub struct MockFeeRange {
1476 pub gas_price: Uniform<u128>,
1478 pub priority_fee: Uniform<u128>,
1480 pub max_fee: Uniform<u128>,
1482 pub max_fee_blob: Uniform<u128>,
1484}
1485
1486impl MockFeeRange {
1487 pub fn new(
1492 gas_price: Range<u128>,
1493 priority_fee: Range<u128>,
1494 max_fee: Range<u128>,
1495 max_fee_blob: Range<u128>,
1496 ) -> Self {
1497 assert!(
1498 max_fee.start >= priority_fee.end,
1499 "max_fee_range should be strictly above the priority fee range"
1500 );
1501 Self {
1502 gas_price: gas_price.try_into().unwrap(),
1503 priority_fee: priority_fee.try_into().unwrap(),
1504 max_fee: max_fee.try_into().unwrap(),
1505 max_fee_blob: max_fee_blob.try_into().unwrap(),
1506 }
1507 }
1508
1509 pub fn sample_gas_price(&self, rng: &mut impl rand::Rng) -> u128 {
1512 self.gas_price.sample(rng)
1513 }
1514
1515 pub fn sample_priority_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1518 self.priority_fee.sample(rng)
1519 }
1520
1521 pub fn sample_max_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1524 self.max_fee.sample(rng)
1525 }
1526
1527 pub fn sample_max_fee_blob(&self, rng: &mut impl rand::Rng) -> u128 {
1530 self.max_fee_blob.sample(rng)
1531 }
1532}
1533
1534#[derive(Debug, Clone)]
1536pub struct MockTransactionDistribution {
1537 transaction_ratio: MockTransactionRatio,
1539 gas_limit_range: Uniform<u64>,
1541 size_range: Uniform<usize>,
1543 fee_ranges: MockFeeRange,
1545}
1546
1547impl MockTransactionDistribution {
1548 pub fn new(
1550 transaction_ratio: MockTransactionRatio,
1551 fee_ranges: MockFeeRange,
1552 gas_limit_range: Range<u64>,
1553 size_range: Range<usize>,
1554 ) -> Self {
1555 Self {
1556 transaction_ratio,
1557 gas_limit_range: gas_limit_range.try_into().unwrap(),
1558 fee_ranges,
1559 size_range: size_range.try_into().unwrap(),
1560 }
1561 }
1562
1563 pub fn tx(&self, nonce: u64, rng: &mut impl rand::Rng) -> MockTransaction {
1565 let transaction_sample = self.transaction_ratio.weighted_index().sample(rng);
1566 let tx = match transaction_sample {
1567 0 => MockTransaction::legacy().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1568 1 => MockTransaction::eip2930().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1569 2 => MockTransaction::eip1559()
1570 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1571 .with_max_fee(self.fee_ranges.sample_max_fee(rng)),
1572 3 => MockTransaction::eip4844()
1573 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1574 .with_max_fee(self.fee_ranges.sample_max_fee(rng))
1575 .with_blob_fee(self.fee_ranges.sample_max_fee_blob(rng)),
1576 _ => unreachable!("unknown transaction type returned by the weighted index"),
1577 };
1578
1579 let size = self.size_range.sample(rng);
1580
1581 tx.with_nonce(nonce).with_gas_limit(self.gas_limit_range.sample(rng)).with_size(size)
1582 }
1583
1584 pub fn tx_set(
1588 &self,
1589 sender: Address,
1590 nonce_range: Range<u64>,
1591 rng: &mut impl rand::Rng,
1592 ) -> MockTransactionSet {
1593 let txs =
1594 nonce_range.map(|nonce| self.tx(nonce, rng).with_sender(sender)).collect::<Vec<_>>();
1595 MockTransactionSet::new(txs)
1596 }
1597
1598 pub fn tx_set_non_conflicting_types(
1604 &self,
1605 sender: Address,
1606 nonce_range: Range<u64>,
1607 rng: &mut impl rand::Rng,
1608 ) -> NonConflictingSetOutcome {
1609 let mut modified_distribution = self.clone();
1617 let first_tx = self.tx(nonce_range.start, rng);
1618
1619 if first_tx.is_eip4844() {
1622 modified_distribution.transaction_ratio = MockTransactionRatio {
1623 legacy_pct: 0,
1624 access_list_pct: 0,
1625 dynamic_fee_pct: 0,
1626 blob_pct: 100,
1627 };
1628
1629 NonConflictingSetOutcome::BlobsOnly(modified_distribution.tx_set(
1631 sender,
1632 nonce_range,
1633 rng,
1634 ))
1635 } else {
1636 let MockTransactionRatio { legacy_pct, access_list_pct, dynamic_fee_pct, .. } =
1637 modified_distribution.transaction_ratio;
1638
1639 let total_non_blob_weight: u32 = legacy_pct + access_list_pct + dynamic_fee_pct;
1641
1642 let new_weights: Vec<u32> = [legacy_pct, access_list_pct, dynamic_fee_pct]
1644 .into_iter()
1645 .map(|weight| weight * 100 / total_non_blob_weight)
1646 .collect();
1647
1648 let new_ratio = MockTransactionRatio {
1649 legacy_pct: new_weights[0],
1650 access_list_pct: new_weights[1],
1651 dynamic_fee_pct: new_weights[2],
1652 blob_pct: 0,
1653 };
1654
1655 modified_distribution.transaction_ratio = new_ratio;
1658
1659 NonConflictingSetOutcome::Mixed(modified_distribution.tx_set(sender, nonce_range, rng))
1661 }
1662 }
1663}
1664
1665#[derive(Debug, Clone)]
1668pub enum NonConflictingSetOutcome {
1669 BlobsOnly(MockTransactionSet),
1671 Mixed(MockTransactionSet),
1673}
1674
1675impl NonConflictingSetOutcome {
1676 pub fn into_inner(self) -> MockTransactionSet {
1678 match self {
1679 Self::BlobsOnly(set) | Self::Mixed(set) => set,
1680 }
1681 }
1682
1683 pub fn with_nonce_gaps(
1691 &mut self,
1692 gap_pct: u32,
1693 gap_range: Range<u64>,
1694 rng: &mut impl rand::Rng,
1695 ) {
1696 match self {
1697 Self::BlobsOnly(_) => {}
1698 Self::Mixed(set) => set.with_nonce_gaps(gap_pct, gap_range, rng),
1699 }
1700 }
1701}
1702
1703#[derive(Debug, Clone)]
1705pub struct MockTransactionSet {
1706 pub(crate) transactions: Vec<MockTransaction>,
1707}
1708
1709impl MockTransactionSet {
1710 const fn new(transactions: Vec<MockTransaction>) -> Self {
1712 Self { transactions }
1713 }
1714
1715 pub fn dependent(sender: Address, from_nonce: u64, tx_count: usize, tx_type: TxType) -> Self {
1722 let mut txs = Vec::with_capacity(tx_count);
1723 let mut curr_tx =
1724 MockTransaction::new_from_type(tx_type).with_nonce(from_nonce).with_sender(sender);
1725 for _ in 0..tx_count {
1726 txs.push(curr_tx.clone());
1727 curr_tx = curr_tx.next();
1728 }
1729
1730 Self::new(txs)
1731 }
1732
1733 pub fn sequential_transactions_by_sender(
1738 sender: Address,
1739 tx_count: usize,
1740 tx_type: TxType,
1741 ) -> Self {
1742 Self::dependent(sender, 0, tx_count, tx_type)
1743 }
1744
1745 pub fn with_nonce_gaps(
1759 &mut self,
1760 gap_pct: u32,
1761 gap_range: Range<u64>,
1762 rng: &mut impl rand::Rng,
1763 ) {
1764 assert!(gap_pct <= 100, "gap_pct must be between 0 and 100");
1765 assert!(gap_range.start >= 1, "gap_range must have a lower bound of at least one");
1766
1767 let mut prev_nonce = 0;
1768 for tx in &mut self.transactions {
1769 if rng.random_bool(gap_pct as f64 / 100.0) {
1770 prev_nonce += gap_range.start;
1771 } else {
1772 prev_nonce += 1;
1773 }
1774 tx.set_nonce(prev_nonce);
1775 }
1776 }
1777
1778 pub fn extend<T: IntoIterator<Item = MockTransaction>>(&mut self, txs: T) {
1780 self.transactions.extend(txs);
1781 }
1782
1783 pub fn into_vec(self) -> Vec<MockTransaction> {
1785 self.transactions
1786 }
1787
1788 pub fn iter(&self) -> impl Iterator<Item = &MockTransaction> {
1790 self.transactions.iter()
1791 }
1792
1793 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MockTransaction> {
1795 self.transactions.iter_mut()
1796 }
1797}
1798
1799impl IntoIterator for MockTransactionSet {
1800 type Item = MockTransaction;
1801 type IntoIter = IntoIter<MockTransaction>;
1802
1803 fn into_iter(self) -> Self::IntoIter {
1804 self.transactions.into_iter()
1805 }
1806}
1807
1808#[test]
1809fn test_mock_priority() {
1810 use crate::TransactionOrdering;
1811
1812 let o = MockOrdering::default();
1813 let lo = MockTransaction::eip1559().with_gas_limit(100_000);
1814 let hi = lo.next().inc_price();
1815 assert!(o.priority(&hi, 0) > o.priority(&lo, 0));
1816}
1817
1818#[cfg(test)]
1819mod tests {
1820 use super::*;
1821 use alloy_consensus::Transaction;
1822 use alloy_primitives::U256;
1823
1824 #[test]
1825 fn test_mock_transaction_factory() {
1826 let mut factory = MockTransactionFactory::default();
1827
1828 let legacy = factory.create_legacy();
1830 assert_eq!(legacy.transaction.tx_type(), TxType::Legacy);
1831
1832 let eip1559 = factory.create_eip1559();
1834 assert_eq!(eip1559.transaction.tx_type(), TxType::Eip1559);
1835
1836 let eip4844 = factory.create_eip4844();
1838 assert_eq!(eip4844.transaction.tx_type(), TxType::Eip4844);
1839 }
1840
1841 #[test]
1842 fn test_mock_transaction_set() {
1843 let sender = Address::random();
1844 let nonce_start = 0u64;
1845 let count = 3;
1846
1847 let legacy_set = MockTransactionSet::dependent(sender, nonce_start, count, TxType::Legacy);
1849 assert_eq!(legacy_set.transactions.len(), count);
1850 for (idx, tx) in legacy_set.transactions.iter().enumerate() {
1851 assert_eq!(tx.tx_type(), TxType::Legacy);
1852 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1853 assert_eq!(tx.sender(), sender);
1854 }
1855
1856 let eip1559_set =
1858 MockTransactionSet::dependent(sender, nonce_start, count, TxType::Eip1559);
1859 assert_eq!(eip1559_set.transactions.len(), count);
1860 for (idx, tx) in eip1559_set.transactions.iter().enumerate() {
1861 assert_eq!(tx.tx_type(), TxType::Eip1559);
1862 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1863 assert_eq!(tx.sender(), sender);
1864 }
1865 }
1866
1867 #[test]
1868 fn test_mock_transaction_modifications() {
1869 let tx = MockTransaction::eip1559();
1870
1871 let original_price = tx.get_gas_price();
1873 let tx_inc = tx.inc_price();
1874 assert!(tx_inc.get_gas_price() > original_price);
1875
1876 let original_limit = tx.gas_limit();
1878 let tx_inc = tx.inc_limit();
1879 assert!(tx_inc.gas_limit() > original_limit);
1880
1881 let original_nonce = tx.nonce();
1883 let tx_inc = tx.inc_nonce();
1884 assert_eq!(tx_inc.nonce(), original_nonce + 1);
1885 }
1886
1887 #[test]
1888 fn test_mock_transaction_cost() {
1889 let tx = MockTransaction::eip1559()
1890 .with_gas_limit(7_000)
1891 .with_max_fee(100)
1892 .with_value(U256::ZERO);
1893
1894 let expected_cost = U256::from(7_000u64) * U256::from(100u128) + U256::ZERO;
1896 assert_eq!(*tx.cost(), expected_cost);
1897 }
1898}