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 eip2930::AccessList,
21 eip4844::{BlobTransactionSidecar, BlobTransactionValidationError, DATA_GAS_PER_BLOB},
22 eip7594::BlobTransactionSidecarVariant,
23 eip7702::SignedAuthorization,
24};
25use alloy_primitives::{Address, Bytes, ChainId, Signature, TxHash, TxKind, B256, U256};
26use paste::paste;
27use rand::{distr::Uniform, prelude::Distribution};
28use reth_ethereum_primitives::{PooledTransactionVariant, Transaction, TransactionSigned};
29use reth_primitives_traits::{
30 transaction::error::TryFromRecoveredTransactionError, InMemorySize, Recovered,
31 SignedTransaction,
32};
33
34use alloy_consensus::error::ValueError;
35use alloy_eips::eip4844::env_settings::KzgSettings;
36use rand::distr::weighted::WeightedIndex;
37use std::{ops::Range, sync::Arc, time::Instant, vec::IntoIter};
38
39pub type MockTxPool = TxPool<MockOrdering>;
43
44pub type MockValidTx = ValidPoolTransaction<MockTransaction>;
49
50pub fn mock_tx_pool() -> MockTxPool {
52 MockTxPool::new(Default::default(), Default::default())
53}
54
55macro_rules! set_value {
57 (&mut $this:expr => $field:ident) => {{
59 let new_value = $field;
60 match $this {
61 MockTransaction::Legacy { $field, .. } => {
62 *$field = new_value;
63 }
64 MockTransaction::Eip1559 { $field, .. } => {
65 *$field = new_value;
66 }
67 MockTransaction::Eip4844 { $field, .. } => {
68 *$field = new_value;
69 }
70 MockTransaction::Eip2930 { $field, .. } => {
71 *$field = new_value;
72 }
73 MockTransaction::Eip7702 { $field, .. } => {
74 *$field = new_value;
75 }
76 }
77 $this.update_cost();
79 }};
80 ($this:expr => $field:ident) => {{
82 let new_value = $field;
83 match $this {
84 MockTransaction::Legacy { ref mut $field, .. } |
85 MockTransaction::Eip1559 { ref mut $field, .. } |
86 MockTransaction::Eip4844 { ref mut $field, .. } |
87 MockTransaction::Eip2930 { ref mut $field, .. } |
88 MockTransaction::Eip7702 { ref mut $field, .. } => {
89 *$field = new_value;
90 }
91 }
92 $this.update_cost();
94 }};
95}
96
97macro_rules! get_value {
99 ($this:tt => $field:ident) => {
100 match $this {
101 MockTransaction::Legacy { $field, .. } |
102 MockTransaction::Eip1559 { $field, .. } |
103 MockTransaction::Eip4844 { $field, .. } |
104 MockTransaction::Eip2930 { $field, .. } |
105 MockTransaction::Eip7702 { $field, .. } => $field,
106 }
107 };
108}
109
110macro_rules! make_setters_getters {
112 ($($name:ident => $t:ty);*) => {
113 paste! {$(
114 pub fn [<set_ $name>](&mut self, $name: $t) -> &mut Self {
116 set_value!(&mut self => $name);
117 self
118 }
119
120 pub fn [<with_ $name>](mut self, $name: $t) -> Self {
122 set_value!(self => $name);
123 self
124 }
125
126 pub const fn [<get_ $name>](&self) -> &$t {
128 get_value!(self => $name)
129 }
130 )*}
131 };
132}
133
134#[derive(Debug, Clone, Eq, PartialEq)]
136pub enum MockTransaction {
137 Legacy {
139 chain_id: Option<ChainId>,
141 hash: B256,
143 sender: Address,
145 nonce: u64,
147 gas_price: u128,
149 gas_limit: u64,
151 to: TxKind,
153 value: U256,
155 input: Bytes,
157 size: usize,
159 cost: U256,
161 },
162 Eip2930 {
164 chain_id: ChainId,
166 hash: B256,
168 sender: Address,
170 nonce: u64,
172 to: TxKind,
174 gas_limit: u64,
176 input: Bytes,
178 value: U256,
180 gas_price: u128,
182 access_list: AccessList,
184 size: usize,
186 cost: U256,
188 },
189 Eip1559 {
191 chain_id: ChainId,
193 hash: B256,
195 sender: Address,
197 nonce: u64,
199 max_fee_per_gas: u128,
201 max_priority_fee_per_gas: u128,
203 gas_limit: u64,
205 to: TxKind,
207 value: U256,
209 access_list: AccessList,
211 input: Bytes,
213 size: usize,
215 cost: U256,
217 },
218 Eip4844 {
220 chain_id: ChainId,
222 hash: B256,
224 sender: Address,
226 nonce: u64,
228 max_fee_per_gas: u128,
230 max_priority_fee_per_gas: u128,
232 max_fee_per_blob_gas: u128,
234 gas_limit: u64,
236 to: Address,
238 value: U256,
240 access_list: AccessList,
242 input: Bytes,
244 sidecar: BlobTransactionSidecarVariant,
246 blob_versioned_hashes: Vec<B256>,
248 size: usize,
250 cost: U256,
252 },
253 Eip7702 {
255 chain_id: ChainId,
257 hash: B256,
259 sender: Address,
261 nonce: u64,
263 max_fee_per_gas: u128,
265 max_priority_fee_per_gas: u128,
267 gas_limit: u64,
269 to: Address,
271 value: U256,
273 access_list: AccessList,
275 authorization_list: Vec<SignedAuthorization>,
277 input: Bytes,
279 size: usize,
281 cost: U256,
283 },
284}
285
286impl MockTransaction {
289 make_setters_getters! {
290 nonce => u64;
291 hash => B256;
292 sender => Address;
293 gas_limit => u64;
294 value => U256;
295 input => Bytes;
296 size => usize
297 }
298
299 pub fn legacy() -> Self {
301 Self::Legacy {
302 chain_id: Some(1),
303 hash: B256::random(),
304 sender: Address::random(),
305 nonce: 0,
306 gas_price: 0,
307 gas_limit: 0,
308 to: Address::random().into(),
309 value: Default::default(),
310 input: Default::default(),
311 size: Default::default(),
312 cost: U256::ZERO,
313 }
314 }
315
316 pub fn eip2930() -> Self {
318 Self::Eip2930 {
319 chain_id: 1,
320 hash: B256::random(),
321 sender: Address::random(),
322 nonce: 0,
323 to: Address::random().into(),
324 gas_limit: 0,
325 input: Bytes::new(),
326 value: Default::default(),
327 gas_price: 0,
328 access_list: Default::default(),
329 size: Default::default(),
330 cost: U256::ZERO,
331 }
332 }
333
334 pub fn eip1559() -> Self {
336 Self::Eip1559 {
337 chain_id: 1,
338 hash: B256::random(),
339 sender: Address::random(),
340 nonce: 0,
341 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
342 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
343 gas_limit: 0,
344 to: Address::random().into(),
345 value: Default::default(),
346 input: Bytes::new(),
347 access_list: Default::default(),
348 size: Default::default(),
349 cost: U256::ZERO,
350 }
351 }
352
353 pub fn eip7702() -> Self {
355 Self::Eip7702 {
356 chain_id: 1,
357 hash: B256::random(),
358 sender: Address::random(),
359 nonce: 0,
360 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
361 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
362 gas_limit: 0,
363 to: Address::random(),
364 value: Default::default(),
365 input: Bytes::new(),
366 access_list: Default::default(),
367 authorization_list: vec![],
368 size: Default::default(),
369 cost: U256::ZERO,
370 }
371 }
372
373 pub fn eip4844() -> Self {
375 Self::Eip4844 {
376 chain_id: 1,
377 hash: B256::random(),
378 sender: Address::random(),
379 nonce: 0,
380 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
381 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
382 max_fee_per_blob_gas: DATA_GAS_PER_BLOB as u128,
383 gas_limit: 0,
384 to: Address::random(),
385 value: Default::default(),
386 input: Bytes::new(),
387 access_list: Default::default(),
388 sidecar: BlobTransactionSidecarVariant::Eip4844(Default::default()),
389 blob_versioned_hashes: Default::default(),
390 size: Default::default(),
391 cost: U256::ZERO,
392 }
393 }
394
395 pub fn eip4844_with_sidecar(sidecar: BlobTransactionSidecarVariant) -> Self {
397 let mut transaction = Self::eip4844();
398 if let Self::Eip4844 { sidecar: existing_sidecar, blob_versioned_hashes, .. } =
399 &mut transaction
400 {
401 *blob_versioned_hashes = sidecar.versioned_hashes().collect();
402 *existing_sidecar = sidecar;
403 }
404 transaction
405 }
406
407 pub fn new_from_type(tx_type: TxType) -> Self {
416 match tx_type {
417 TxType::Legacy => Self::legacy(),
418 TxType::Eip2930 => Self::eip2930(),
419 TxType::Eip1559 => Self::eip1559(),
420 TxType::Eip4844 => Self::eip4844(),
421 TxType::Eip7702 => Self::eip7702(),
422 }
423 }
424
425 pub const fn with_blob_fee(mut self, val: u128) -> Self {
427 self.set_blob_fee(val);
428 self
429 }
430
431 pub const fn set_blob_fee(&mut self, val: u128) -> &mut Self {
433 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = self {
434 *max_fee_per_blob_gas = val;
435 }
436 self
437 }
438
439 pub const fn set_priority_fee(&mut self, val: u128) -> &mut Self {
441 if let Self::Eip1559 { max_priority_fee_per_gas, .. } |
442 Self::Eip4844 { max_priority_fee_per_gas, .. } = self
443 {
444 *max_priority_fee_per_gas = val;
445 }
446 self
447 }
448
449 pub const fn with_priority_fee(mut self, val: u128) -> Self {
451 self.set_priority_fee(val);
452 self
453 }
454
455 pub const fn get_priority_fee(&self) -> Option<u128> {
457 match self {
458 Self::Eip1559 { max_priority_fee_per_gas, .. } |
459 Self::Eip4844 { max_priority_fee_per_gas, .. } |
460 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
461 _ => None,
462 }
463 }
464
465 pub const fn set_max_fee(&mut self, val: u128) -> &mut Self {
467 if let Self::Eip1559 { max_fee_per_gas, .. } |
468 Self::Eip4844 { max_fee_per_gas, .. } |
469 Self::Eip7702 { max_fee_per_gas, .. } = self
470 {
471 *max_fee_per_gas = val;
472 }
473 self
474 }
475
476 pub const fn with_max_fee(mut self, val: u128) -> Self {
478 self.set_max_fee(val);
479 self
480 }
481
482 pub const fn get_max_fee(&self) -> Option<u128> {
484 match self {
485 Self::Eip1559 { max_fee_per_gas, .. } |
486 Self::Eip4844 { max_fee_per_gas, .. } |
487 Self::Eip7702 { max_fee_per_gas, .. } => Some(*max_fee_per_gas),
488 _ => None,
489 }
490 }
491
492 pub fn set_accesslist(&mut self, list: AccessList) -> &mut Self {
494 match self {
495 Self::Legacy { .. } => {}
496 Self::Eip1559 { access_list: accesslist, .. } |
497 Self::Eip4844 { access_list: accesslist, .. } |
498 Self::Eip2930 { access_list: accesslist, .. } |
499 Self::Eip7702 { access_list: accesslist, .. } => {
500 *accesslist = list;
501 }
502 }
503 self
504 }
505
506 pub fn set_authorization_list(&mut self, list: Vec<SignedAuthorization>) -> &mut Self {
508 if let Self::Eip7702 { authorization_list, .. } = self {
509 *authorization_list = list;
510 }
511
512 self
513 }
514
515 pub const fn set_gas_price(&mut self, val: u128) -> &mut Self {
517 match self {
518 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => {
519 *gas_price = val;
520 }
521 Self::Eip1559 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
522 Self::Eip4844 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
523 Self::Eip7702 { max_fee_per_gas, max_priority_fee_per_gas, .. } => {
524 *max_fee_per_gas = val;
525 *max_priority_fee_per_gas = val;
526 }
527 }
528 self
529 }
530
531 pub const fn with_gas_price(mut self, val: u128) -> Self {
533 match self {
534 Self::Legacy { ref mut gas_price, .. } | Self::Eip2930 { ref mut gas_price, .. } => {
535 *gas_price = val;
536 }
537 Self::Eip1559 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
538 Self::Eip4844 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
539 Self::Eip7702 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } => {
540 *max_fee_per_gas = val;
541 *max_priority_fee_per_gas = val;
542 }
543 }
544 self
545 }
546
547 pub const fn get_gas_price(&self) -> u128 {
549 match self {
550 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
551 Self::Eip1559 { max_fee_per_gas, .. } |
552 Self::Eip4844 { max_fee_per_gas, .. } |
553 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
554 }
555 }
556
557 pub fn prev(&self) -> Self {
559 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() - 1)
560 }
561
562 pub fn next(&self) -> Self {
564 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + 1)
565 }
566
567 pub fn skip(&self, skip: u64) -> Self {
569 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + skip + 1)
570 }
571
572 pub fn inc_nonce(self) -> Self {
574 let nonce = self.get_nonce() + 1;
575 self.with_nonce(nonce)
576 }
577
578 pub fn rng_hash(self) -> Self {
580 self.with_hash(B256::random())
581 }
582
583 pub fn inc_price(&self) -> Self {
585 self.inc_price_by(1)
586 }
587
588 pub fn inc_price_by(&self, value: u128) -> Self {
590 self.clone().with_gas_price(self.get_gas_price().checked_add(value).unwrap())
591 }
592
593 pub fn decr_price(&self) -> Self {
595 self.decr_price_by(1)
596 }
597
598 pub fn decr_price_by(&self, value: u128) -> Self {
600 self.clone().with_gas_price(self.get_gas_price().checked_sub(value).unwrap())
601 }
602
603 pub fn inc_value(&self) -> Self {
605 self.clone().with_value(self.get_value().checked_add(U256::from(1)).unwrap())
606 }
607
608 pub fn inc_limit(&self) -> Self {
610 self.clone().with_gas_limit(self.get_gas_limit() + 1)
611 }
612
613 pub fn inc_blob_fee(&self) -> Self {
617 self.inc_blob_fee_by(1)
618 }
619
620 pub fn inc_blob_fee_by(&self, value: u128) -> Self {
624 let mut this = self.clone();
625 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
626 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_add(value).unwrap();
627 }
628 this
629 }
630
631 pub fn decr_blob_fee(&self) -> Self {
635 self.decr_price_by(1)
636 }
637
638 pub fn decr_blob_fee_by(&self, value: u128) -> Self {
642 let mut this = self.clone();
643 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
644 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_sub(value).unwrap();
645 }
646 this
647 }
648
649 pub const fn tx_type(&self) -> u8 {
651 match self {
652 Self::Legacy { .. } => LEGACY_TX_TYPE_ID,
653 Self::Eip1559 { .. } => EIP1559_TX_TYPE_ID,
654 Self::Eip4844 { .. } => EIP4844_TX_TYPE_ID,
655 Self::Eip2930 { .. } => EIP2930_TX_TYPE_ID,
656 Self::Eip7702 { .. } => EIP7702_TX_TYPE_ID,
657 }
658 }
659
660 pub const fn is_legacy(&self) -> bool {
662 matches!(self, Self::Legacy { .. })
663 }
664
665 pub const fn is_eip1559(&self) -> bool {
667 matches!(self, Self::Eip1559 { .. })
668 }
669
670 pub const fn is_eip4844(&self) -> bool {
672 matches!(self, Self::Eip4844 { .. })
673 }
674
675 pub const fn is_eip2930(&self) -> bool {
677 matches!(self, Self::Eip2930 { .. })
678 }
679
680 pub const fn is_eip7702(&self) -> bool {
682 matches!(self, Self::Eip7702 { .. })
683 }
684
685 fn update_cost(&mut self) {
686 match self {
687 Self::Legacy { cost, gas_limit, gas_price, value, .. } |
688 Self::Eip2930 { cost, gas_limit, gas_price, value, .. } => {
689 *cost = U256::from(*gas_limit) * U256::from(*gas_price) + *value
690 }
691 Self::Eip1559 { cost, gas_limit, max_fee_per_gas, value, .. } |
692 Self::Eip4844 { cost, gas_limit, max_fee_per_gas, value, .. } |
693 Self::Eip7702 { cost, gas_limit, max_fee_per_gas, value, .. } => {
694 *cost = U256::from(*gas_limit) * U256::from(*max_fee_per_gas) + *value
695 }
696 };
697 }
698}
699
700impl PoolTransaction for MockTransaction {
701 type TryFromConsensusError = ValueError<EthereumTxEnvelope<TxEip4844>>;
702
703 type Consensus = TransactionSigned;
704
705 type Pooled = PooledTransactionVariant;
706
707 fn into_consensus(self) -> Recovered<Self::Consensus> {
708 self.into()
709 }
710
711 fn from_pooled(pooled: Recovered<Self::Pooled>) -> Self {
712 pooled.into()
713 }
714
715 fn hash(&self) -> &TxHash {
716 self.get_hash()
717 }
718
719 fn sender(&self) -> Address {
720 *self.get_sender()
721 }
722
723 fn sender_ref(&self) -> &Address {
724 self.get_sender()
725 }
726
727 fn cost(&self) -> &U256 {
732 match self {
733 Self::Legacy { cost, .. } |
734 Self::Eip2930 { cost, .. } |
735 Self::Eip1559 { cost, .. } |
736 Self::Eip4844 { cost, .. } |
737 Self::Eip7702 { cost, .. } => cost,
738 }
739 }
740
741 fn encoded_length(&self) -> usize {
743 self.size()
744 }
745}
746
747impl InMemorySize for MockTransaction {
748 fn size(&self) -> usize {
749 *self.get_size()
750 }
751}
752
753impl Typed2718 for MockTransaction {
754 fn ty(&self) -> u8 {
755 match self {
756 Self::Legacy { .. } => TxType::Legacy.into(),
757 Self::Eip1559 { .. } => TxType::Eip1559.into(),
758 Self::Eip4844 { .. } => TxType::Eip4844.into(),
759 Self::Eip2930 { .. } => TxType::Eip2930.into(),
760 Self::Eip7702 { .. } => TxType::Eip7702.into(),
761 }
762 }
763}
764
765impl alloy_consensus::Transaction for MockTransaction {
766 fn chain_id(&self) -> Option<u64> {
767 match self {
768 Self::Legacy { chain_id, .. } => *chain_id,
769 Self::Eip1559 { chain_id, .. } |
770 Self::Eip4844 { chain_id, .. } |
771 Self::Eip2930 { chain_id, .. } |
772 Self::Eip7702 { chain_id, .. } => Some(*chain_id),
773 }
774 }
775
776 fn nonce(&self) -> u64 {
777 *self.get_nonce()
778 }
779
780 fn gas_limit(&self) -> u64 {
781 *self.get_gas_limit()
782 }
783
784 fn gas_price(&self) -> Option<u128> {
785 match self {
786 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => Some(*gas_price),
787 _ => None,
788 }
789 }
790
791 fn max_fee_per_gas(&self) -> u128 {
792 match self {
793 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
794 Self::Eip1559 { max_fee_per_gas, .. } |
795 Self::Eip4844 { max_fee_per_gas, .. } |
796 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
797 }
798 }
799
800 fn max_priority_fee_per_gas(&self) -> Option<u128> {
801 match self {
802 Self::Legacy { .. } | Self::Eip2930 { .. } => None,
803 Self::Eip1559 { max_priority_fee_per_gas, .. } |
804 Self::Eip4844 { max_priority_fee_per_gas, .. } |
805 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
806 }
807 }
808
809 fn max_fee_per_blob_gas(&self) -> Option<u128> {
810 match self {
811 Self::Eip4844 { max_fee_per_blob_gas, .. } => Some(*max_fee_per_blob_gas),
812 _ => None,
813 }
814 }
815
816 fn priority_fee_or_price(&self) -> u128 {
817 match self {
818 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
819 Self::Eip1559 { max_priority_fee_per_gas, .. } |
820 Self::Eip4844 { max_priority_fee_per_gas, .. } |
821 Self::Eip7702 { max_priority_fee_per_gas, .. } => *max_priority_fee_per_gas,
822 }
823 }
824
825 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
826 base_fee.map_or_else(
827 || self.max_fee_per_gas(),
828 |base_fee| {
829 let tip = self.max_fee_per_gas().saturating_sub(base_fee as u128);
832 if let Some(max_tip) = self.max_priority_fee_per_gas() {
833 if tip > max_tip {
834 max_tip + base_fee as u128
835 } else {
836 self.max_fee_per_gas()
838 }
839 } else {
840 self.max_fee_per_gas()
841 }
842 },
843 )
844 }
845
846 fn is_dynamic_fee(&self) -> bool {
847 !matches!(self, Self::Legacy { .. } | Self::Eip2930 { .. })
848 }
849
850 fn kind(&self) -> TxKind {
851 match self {
852 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => *to,
853 Self::Eip4844 { to, .. } | Self::Eip7702 { to, .. } => TxKind::Call(*to),
854 }
855 }
856
857 fn is_create(&self) -> bool {
858 match self {
859 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => {
860 to.is_create()
861 }
862 Self::Eip4844 { .. } | Self::Eip7702 { .. } => false,
863 }
864 }
865
866 fn value(&self) -> U256 {
867 match self {
868 Self::Legacy { value, .. } |
869 Self::Eip1559 { value, .. } |
870 Self::Eip2930 { value, .. } |
871 Self::Eip4844 { value, .. } |
872 Self::Eip7702 { value, .. } => *value,
873 }
874 }
875
876 fn input(&self) -> &Bytes {
877 self.get_input()
878 }
879
880 fn access_list(&self) -> Option<&AccessList> {
881 match self {
882 Self::Legacy { .. } => None,
883 Self::Eip1559 { access_list: accesslist, .. } |
884 Self::Eip4844 { access_list: accesslist, .. } |
885 Self::Eip2930 { access_list: accesslist, .. } |
886 Self::Eip7702 { access_list: accesslist, .. } => Some(accesslist),
887 }
888 }
889
890 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
891 match self {
892 Self::Eip4844 { blob_versioned_hashes, .. } => Some(blob_versioned_hashes),
893 _ => None,
894 }
895 }
896
897 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
898 match self {
899 Self::Eip7702 { authorization_list, .. } => Some(authorization_list),
900 _ => None,
901 }
902 }
903}
904
905impl EthPoolTransaction for MockTransaction {
906 fn take_blob(&mut self) -> EthBlobTransactionSidecar {
907 match self {
908 Self::Eip4844 { sidecar, .. } => EthBlobTransactionSidecar::Present(sidecar.clone()),
909 _ => EthBlobTransactionSidecar::None,
910 }
911 }
912
913 fn try_into_pooled_eip4844(
914 self,
915 sidecar: Arc<BlobTransactionSidecarVariant>,
916 ) -> Option<Recovered<Self::Pooled>> {
917 let (tx, signer) = self.into_consensus().into_parts();
918 tx.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar))
919 .map(|tx| tx.with_signer(signer))
920 .ok()
921 }
922
923 fn try_from_eip4844(
924 tx: Recovered<Self::Consensus>,
925 sidecar: BlobTransactionSidecarVariant,
926 ) -> Option<Self> {
927 let (tx, signer) = tx.into_parts();
928 tx.try_into_pooled_eip4844(sidecar)
929 .map(|tx| tx.with_signer(signer))
930 .ok()
931 .map(Self::from_pooled)
932 }
933
934 fn validate_blob(
935 &self,
936 _blob: &BlobTransactionSidecarVariant,
937 _settings: &KzgSettings,
938 ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> {
939 match &self {
940 Self::Eip4844 { .. } => Ok(()),
941 _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
942 }
943 }
944}
945
946impl TryFrom<Recovered<TransactionSigned>> for MockTransaction {
947 type Error = TryFromRecoveredTransactionError;
948
949 fn try_from(tx: Recovered<TransactionSigned>) -> Result<Self, Self::Error> {
950 let sender = tx.signer();
951 let transaction = tx.into_inner();
952 let hash = *transaction.tx_hash();
953 let size = transaction.size();
954
955 match transaction.into_typed_transaction() {
956 Transaction::Legacy(TxLegacy {
957 chain_id,
958 nonce,
959 gas_price,
960 gas_limit,
961 to,
962 value,
963 input,
964 }) => Ok(Self::Legacy {
965 chain_id,
966 hash,
967 sender,
968 nonce,
969 gas_price,
970 gas_limit,
971 to,
972 value,
973 input,
974 size,
975 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
976 }),
977 Transaction::Eip2930(TxEip2930 {
978 chain_id,
979 nonce,
980 gas_price,
981 gas_limit,
982 to,
983 value,
984 input,
985 access_list,
986 }) => Ok(Self::Eip2930 {
987 chain_id,
988 hash,
989 sender,
990 nonce,
991 gas_price,
992 gas_limit,
993 to,
994 value,
995 input,
996 access_list,
997 size,
998 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
999 }),
1000 Transaction::Eip1559(TxEip1559 {
1001 chain_id,
1002 nonce,
1003 gas_limit,
1004 max_fee_per_gas,
1005 max_priority_fee_per_gas,
1006 to,
1007 value,
1008 input,
1009 access_list,
1010 }) => Ok(Self::Eip1559 {
1011 chain_id,
1012 hash,
1013 sender,
1014 nonce,
1015 max_fee_per_gas,
1016 max_priority_fee_per_gas,
1017 gas_limit,
1018 to,
1019 value,
1020 input,
1021 access_list,
1022 size,
1023 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1024 }),
1025 Transaction::Eip4844(TxEip4844 {
1026 chain_id,
1027 nonce,
1028 gas_limit,
1029 max_fee_per_gas,
1030 max_priority_fee_per_gas,
1031 to,
1032 value,
1033 input,
1034 access_list,
1035 blob_versioned_hashes: _,
1036 max_fee_per_blob_gas,
1037 }) => Ok(Self::Eip4844 {
1038 chain_id,
1039 hash,
1040 sender,
1041 nonce,
1042 max_fee_per_gas,
1043 max_priority_fee_per_gas,
1044 max_fee_per_blob_gas,
1045 gas_limit,
1046 to,
1047 value,
1048 input,
1049 access_list,
1050 sidecar: BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default()),
1051 blob_versioned_hashes: Default::default(),
1052 size,
1053 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1054 }),
1055 Transaction::Eip7702(TxEip7702 {
1056 chain_id,
1057 nonce,
1058 gas_limit,
1059 max_fee_per_gas,
1060 max_priority_fee_per_gas,
1061 to,
1062 value,
1063 access_list,
1064 authorization_list,
1065 input,
1066 }) => Ok(Self::Eip7702 {
1067 chain_id,
1068 hash,
1069 sender,
1070 nonce,
1071 max_fee_per_gas,
1072 max_priority_fee_per_gas,
1073 gas_limit,
1074 to,
1075 value,
1076 input,
1077 access_list,
1078 authorization_list,
1079 size,
1080 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1081 }),
1082 }
1083 }
1084}
1085
1086impl TryFrom<Recovered<EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>>>
1087 for MockTransaction
1088{
1089 type Error = TryFromRecoveredTransactionError;
1090
1091 fn try_from(
1092 tx: Recovered<EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>>,
1093 ) -> Result<Self, Self::Error> {
1094 let sender = tx.signer();
1095 let transaction = tx.into_inner();
1096 let hash = *transaction.tx_hash();
1097 let size = transaction.size();
1098
1099 match transaction {
1100 EthereumTxEnvelope::Legacy(signed_tx) => {
1101 let tx = signed_tx.strip_signature();
1102 Ok(Self::Legacy {
1103 chain_id: tx.chain_id,
1104 hash,
1105 sender,
1106 nonce: tx.nonce,
1107 gas_price: tx.gas_price,
1108 gas_limit: tx.gas_limit,
1109 to: tx.to,
1110 value: tx.value,
1111 input: tx.input,
1112 size,
1113 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1114 })
1115 }
1116 EthereumTxEnvelope::Eip2930(signed_tx) => {
1117 let tx = signed_tx.strip_signature();
1118 Ok(Self::Eip2930 {
1119 chain_id: tx.chain_id,
1120 hash,
1121 sender,
1122 nonce: tx.nonce,
1123 gas_price: tx.gas_price,
1124 gas_limit: tx.gas_limit,
1125 to: tx.to,
1126 value: tx.value,
1127 input: tx.input,
1128 access_list: tx.access_list,
1129 size,
1130 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1131 })
1132 }
1133 EthereumTxEnvelope::Eip1559(signed_tx) => {
1134 let tx = signed_tx.strip_signature();
1135 Ok(Self::Eip1559 {
1136 chain_id: tx.chain_id,
1137 hash,
1138 sender,
1139 nonce: tx.nonce,
1140 max_fee_per_gas: tx.max_fee_per_gas,
1141 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1142 gas_limit: tx.gas_limit,
1143 to: tx.to,
1144 value: tx.value,
1145 input: tx.input,
1146 access_list: tx.access_list,
1147 size,
1148 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1149 })
1150 }
1151 EthereumTxEnvelope::Eip4844(signed_tx) => match signed_tx.tx() {
1152 TxEip4844Variant::TxEip4844(tx) => Ok(Self::Eip4844 {
1153 chain_id: tx.chain_id,
1154 hash,
1155 sender,
1156 nonce: tx.nonce,
1157 max_fee_per_gas: tx.max_fee_per_gas,
1158 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1159 max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
1160 gas_limit: tx.gas_limit,
1161 to: tx.to,
1162 value: tx.value,
1163 input: tx.input.clone(),
1164 access_list: tx.access_list.clone(),
1165 sidecar: BlobTransactionSidecarVariant::Eip4844(
1166 BlobTransactionSidecar::default(),
1167 ),
1168 blob_versioned_hashes: tx.blob_versioned_hashes.clone(),
1169 size,
1170 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1171 }),
1172 tx => Err(TryFromRecoveredTransactionError::UnsupportedTransactionType(tx.ty())),
1173 },
1174 EthereumTxEnvelope::Eip7702(signed_tx) => {
1175 let tx = signed_tx.strip_signature();
1176 Ok(Self::Eip7702 {
1177 chain_id: tx.chain_id,
1178 hash,
1179 sender,
1180 nonce: tx.nonce,
1181 max_fee_per_gas: tx.max_fee_per_gas,
1182 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1183 gas_limit: tx.gas_limit,
1184 to: tx.to,
1185 value: tx.value,
1186 access_list: tx.access_list,
1187 authorization_list: tx.authorization_list,
1188 input: tx.input,
1189 size,
1190 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1191 })
1192 }
1193 }
1194 }
1195}
1196
1197impl From<Recovered<PooledTransactionVariant>> for MockTransaction {
1198 fn from(tx: Recovered<PooledTransactionVariant>) -> Self {
1199 let (tx, signer) = tx.into_parts();
1200 Recovered::<TransactionSigned>::new_unchecked(tx.into(), signer).try_into().expect(
1201 "Failed to convert from PooledTransactionsElementEcRecovered to MockTransaction",
1202 )
1203 }
1204}
1205
1206impl From<MockTransaction> for Recovered<TransactionSigned> {
1207 fn from(tx: MockTransaction) -> Self {
1208 let hash = *tx.hash();
1209 let sender = tx.sender();
1210 let tx = Transaction::from(tx);
1211 let tx: TransactionSigned =
1212 Signed::new_unchecked(tx, Signature::test_signature(), hash).into();
1213 Self::new_unchecked(tx, sender)
1214 }
1215}
1216
1217impl From<MockTransaction> for Transaction {
1218 fn from(mock: MockTransaction) -> Self {
1219 match mock {
1220 MockTransaction::Legacy {
1221 chain_id,
1222 nonce,
1223 gas_price,
1224 gas_limit,
1225 to,
1226 value,
1227 input,
1228 ..
1229 } => Self::Legacy(TxLegacy { chain_id, nonce, gas_price, gas_limit, to, value, input }),
1230 MockTransaction::Eip2930 {
1231 chain_id,
1232 nonce,
1233 gas_price,
1234 gas_limit,
1235 to,
1236 value,
1237 access_list,
1238 input,
1239 ..
1240 } => Self::Eip2930(TxEip2930 {
1241 chain_id,
1242 nonce,
1243 gas_price,
1244 gas_limit,
1245 to,
1246 value,
1247 access_list,
1248 input,
1249 }),
1250 MockTransaction::Eip1559 {
1251 chain_id,
1252 nonce,
1253 gas_limit,
1254 max_fee_per_gas,
1255 max_priority_fee_per_gas,
1256 to,
1257 value,
1258 access_list,
1259 input,
1260 ..
1261 } => Self::Eip1559(TxEip1559 {
1262 chain_id,
1263 nonce,
1264 gas_limit,
1265 max_fee_per_gas,
1266 max_priority_fee_per_gas,
1267 to,
1268 value,
1269 access_list,
1270 input,
1271 }),
1272 MockTransaction::Eip4844 {
1273 chain_id,
1274 nonce,
1275 gas_limit,
1276 max_fee_per_gas,
1277 max_priority_fee_per_gas,
1278 to,
1279 value,
1280 access_list,
1281 sidecar,
1282 max_fee_per_blob_gas,
1283 input,
1284 ..
1285 } => Self::Eip4844(TxEip4844 {
1286 chain_id,
1287 nonce,
1288 gas_limit,
1289 max_fee_per_gas,
1290 max_priority_fee_per_gas,
1291 to,
1292 value,
1293 access_list,
1294 blob_versioned_hashes: sidecar.versioned_hashes().collect(),
1295 max_fee_per_blob_gas,
1296 input,
1297 }),
1298 MockTransaction::Eip7702 {
1299 chain_id,
1300 nonce,
1301 gas_limit,
1302 max_fee_per_gas,
1303 max_priority_fee_per_gas,
1304 to,
1305 value,
1306 access_list,
1307 input,
1308 authorization_list,
1309 ..
1310 } => Self::Eip7702(TxEip7702 {
1311 chain_id,
1312 nonce,
1313 gas_limit,
1314 max_fee_per_gas,
1315 max_priority_fee_per_gas,
1316 to,
1317 value,
1318 access_list,
1319 authorization_list,
1320 input,
1321 }),
1322 }
1323 }
1324}
1325
1326#[cfg(any(test, feature = "arbitrary"))]
1327impl proptest::arbitrary::Arbitrary for MockTransaction {
1328 type Parameters = ();
1329 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1330 use proptest::prelude::Strategy;
1331 use proptest_arbitrary_interop::arb;
1332
1333 arb::<(TransactionSigned, Address)>()
1334 .prop_map(|(signed_transaction, signer)| {
1335 Recovered::new_unchecked(signed_transaction, signer)
1336 .try_into()
1337 .expect("Failed to create an Arbitrary MockTransaction from a Recovered tx")
1338 })
1339 .boxed()
1340 }
1341
1342 type Strategy = proptest::strategy::BoxedStrategy<Self>;
1343}
1344
1345#[derive(Debug, Default)]
1347pub struct MockTransactionFactory {
1348 pub(crate) ids: SenderIdentifiers,
1349}
1350
1351impl MockTransactionFactory {
1354 pub fn tx_id(&mut self, tx: &MockTransaction) -> TransactionId {
1356 let sender = self.ids.sender_id_or_create(tx.sender());
1357 TransactionId::new(sender, *tx.get_nonce())
1358 }
1359
1360 pub fn validated(&mut self, transaction: MockTransaction) -> MockValidTx {
1362 self.validated_with_origin(TransactionOrigin::External, transaction)
1363 }
1364
1365 pub fn validated_arc(&mut self, transaction: MockTransaction) -> Arc<MockValidTx> {
1367 Arc::new(self.validated(transaction))
1368 }
1369
1370 pub fn validated_with_origin(
1372 &mut self,
1373 origin: TransactionOrigin,
1374 transaction: MockTransaction,
1375 ) -> MockValidTx {
1376 MockValidTx {
1377 propagate: false,
1378 transaction_id: self.tx_id(&transaction),
1379 transaction,
1380 timestamp: Instant::now(),
1381 origin,
1382 authority_ids: None,
1383 }
1384 }
1385
1386 pub fn create_legacy(&mut self) -> MockValidTx {
1388 self.validated(MockTransaction::legacy())
1389 }
1390
1391 pub fn create_eip1559(&mut self) -> MockValidTx {
1393 self.validated(MockTransaction::eip1559())
1394 }
1395
1396 pub fn create_eip4844(&mut self) -> MockValidTx {
1398 self.validated(MockTransaction::eip4844())
1399 }
1400}
1401
1402pub type MockOrdering = CoinbaseTipOrdering<MockTransaction>;
1404
1405#[derive(Debug, Clone)]
1408pub struct MockTransactionRatio {
1409 pub legacy_pct: u32,
1411 pub access_list_pct: u32,
1413 pub dynamic_fee_pct: u32,
1415 pub blob_pct: u32,
1417}
1418
1419impl MockTransactionRatio {
1420 pub fn new(legacy_pct: u32, access_list_pct: u32, dynamic_fee_pct: u32, blob_pct: u32) -> Self {
1426 let total = legacy_pct + access_list_pct + dynamic_fee_pct + blob_pct;
1427 assert_eq!(
1428 total,
1429 100,
1430 "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}",
1431 );
1432
1433 Self { legacy_pct, access_list_pct, dynamic_fee_pct, blob_pct }
1434 }
1435
1436 pub fn weighted_index(&self) -> WeightedIndex<u32> {
1444 WeightedIndex::new([
1445 self.legacy_pct,
1446 self.access_list_pct,
1447 self.dynamic_fee_pct,
1448 self.blob_pct,
1449 ])
1450 .unwrap()
1451 }
1452}
1453
1454#[derive(Debug, Clone)]
1456pub struct MockFeeRange {
1457 pub gas_price: Uniform<u128>,
1459 pub priority_fee: Uniform<u128>,
1461 pub max_fee: Uniform<u128>,
1463 pub max_fee_blob: Uniform<u128>,
1465}
1466
1467impl MockFeeRange {
1468 pub fn new(
1473 gas_price: Range<u128>,
1474 priority_fee: Range<u128>,
1475 max_fee: Range<u128>,
1476 max_fee_blob: Range<u128>,
1477 ) -> Self {
1478 assert!(
1479 max_fee.start >= priority_fee.end,
1480 "max_fee_range should be strictly above the priority fee range"
1481 );
1482 Self {
1483 gas_price: gas_price.try_into().unwrap(),
1484 priority_fee: priority_fee.try_into().unwrap(),
1485 max_fee: max_fee.try_into().unwrap(),
1486 max_fee_blob: max_fee_blob.try_into().unwrap(),
1487 }
1488 }
1489
1490 pub fn sample_gas_price(&self, rng: &mut impl rand::Rng) -> u128 {
1493 self.gas_price.sample(rng)
1494 }
1495
1496 pub fn sample_priority_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1499 self.priority_fee.sample(rng)
1500 }
1501
1502 pub fn sample_max_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1505 self.max_fee.sample(rng)
1506 }
1507
1508 pub fn sample_max_fee_blob(&self, rng: &mut impl rand::Rng) -> u128 {
1511 self.max_fee_blob.sample(rng)
1512 }
1513}
1514
1515#[derive(Debug, Clone)]
1517pub struct MockTransactionDistribution {
1518 transaction_ratio: MockTransactionRatio,
1520 gas_limit_range: Uniform<u64>,
1522 size_range: Uniform<usize>,
1524 fee_ranges: MockFeeRange,
1526}
1527
1528impl MockTransactionDistribution {
1529 pub fn new(
1531 transaction_ratio: MockTransactionRatio,
1532 fee_ranges: MockFeeRange,
1533 gas_limit_range: Range<u64>,
1534 size_range: Range<usize>,
1535 ) -> Self {
1536 Self {
1537 transaction_ratio,
1538 gas_limit_range: gas_limit_range.try_into().unwrap(),
1539 fee_ranges,
1540 size_range: size_range.try_into().unwrap(),
1541 }
1542 }
1543
1544 pub fn tx(&self, nonce: u64, rng: &mut impl rand::Rng) -> MockTransaction {
1546 let transaction_sample = self.transaction_ratio.weighted_index().sample(rng);
1547 let tx = match transaction_sample {
1548 0 => MockTransaction::legacy().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1549 1 => MockTransaction::eip2930().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1550 2 => MockTransaction::eip1559()
1551 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1552 .with_max_fee(self.fee_ranges.sample_max_fee(rng)),
1553 3 => MockTransaction::eip4844()
1554 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1555 .with_max_fee(self.fee_ranges.sample_max_fee(rng))
1556 .with_blob_fee(self.fee_ranges.sample_max_fee_blob(rng)),
1557 _ => unreachable!("unknown transaction type returned by the weighted index"),
1558 };
1559
1560 let size = self.size_range.sample(rng);
1561
1562 tx.with_nonce(nonce).with_gas_limit(self.gas_limit_range.sample(rng)).with_size(size)
1563 }
1564
1565 pub fn tx_set(
1569 &self,
1570 sender: Address,
1571 nonce_range: Range<u64>,
1572 rng: &mut impl rand::Rng,
1573 ) -> MockTransactionSet {
1574 let txs =
1575 nonce_range.map(|nonce| self.tx(nonce, rng).with_sender(sender)).collect::<Vec<_>>();
1576 MockTransactionSet::new(txs)
1577 }
1578
1579 pub fn tx_set_non_conflicting_types(
1585 &self,
1586 sender: Address,
1587 nonce_range: Range<u64>,
1588 rng: &mut impl rand::Rng,
1589 ) -> NonConflictingSetOutcome {
1590 let mut modified_distribution = self.clone();
1598 let first_tx = self.tx(nonce_range.start, rng);
1599
1600 if first_tx.is_eip4844() {
1603 modified_distribution.transaction_ratio = MockTransactionRatio {
1604 legacy_pct: 0,
1605 access_list_pct: 0,
1606 dynamic_fee_pct: 0,
1607 blob_pct: 100,
1608 };
1609
1610 NonConflictingSetOutcome::BlobsOnly(modified_distribution.tx_set(
1612 sender,
1613 nonce_range,
1614 rng,
1615 ))
1616 } else {
1617 let MockTransactionRatio { legacy_pct, access_list_pct, dynamic_fee_pct, .. } =
1618 modified_distribution.transaction_ratio;
1619
1620 let total_non_blob_weight: u32 = legacy_pct + access_list_pct + dynamic_fee_pct;
1622
1623 let new_weights: Vec<u32> = [legacy_pct, access_list_pct, dynamic_fee_pct]
1625 .into_iter()
1626 .map(|weight| weight * 100 / total_non_blob_weight)
1627 .collect();
1628
1629 let new_ratio = MockTransactionRatio {
1630 legacy_pct: new_weights[0],
1631 access_list_pct: new_weights[1],
1632 dynamic_fee_pct: new_weights[2],
1633 blob_pct: 0,
1634 };
1635
1636 modified_distribution.transaction_ratio = new_ratio;
1639
1640 NonConflictingSetOutcome::Mixed(modified_distribution.tx_set(sender, nonce_range, rng))
1642 }
1643 }
1644}
1645
1646#[derive(Debug, Clone)]
1649pub enum NonConflictingSetOutcome {
1650 BlobsOnly(MockTransactionSet),
1652 Mixed(MockTransactionSet),
1654}
1655
1656impl NonConflictingSetOutcome {
1657 pub fn into_inner(self) -> MockTransactionSet {
1659 match self {
1660 Self::BlobsOnly(set) | Self::Mixed(set) => set,
1661 }
1662 }
1663
1664 pub fn with_nonce_gaps(
1672 &mut self,
1673 gap_pct: u32,
1674 gap_range: Range<u64>,
1675 rng: &mut impl rand::Rng,
1676 ) {
1677 match self {
1678 Self::BlobsOnly(_) => {}
1679 Self::Mixed(set) => set.with_nonce_gaps(gap_pct, gap_range, rng),
1680 }
1681 }
1682}
1683
1684#[derive(Debug, Clone)]
1686pub struct MockTransactionSet {
1687 pub(crate) transactions: Vec<MockTransaction>,
1688}
1689
1690impl MockTransactionSet {
1691 const fn new(transactions: Vec<MockTransaction>) -> Self {
1693 Self { transactions }
1694 }
1695
1696 pub fn dependent(sender: Address, from_nonce: u64, tx_count: usize, tx_type: TxType) -> Self {
1703 let mut txs = Vec::with_capacity(tx_count);
1704 let mut curr_tx =
1705 MockTransaction::new_from_type(tx_type).with_nonce(from_nonce).with_sender(sender);
1706 for _ in 0..tx_count {
1707 txs.push(curr_tx.clone());
1708 curr_tx = curr_tx.next();
1709 }
1710
1711 Self::new(txs)
1712 }
1713
1714 pub fn sequential_transactions_by_sender(
1719 sender: Address,
1720 tx_count: usize,
1721 tx_type: TxType,
1722 ) -> Self {
1723 Self::dependent(sender, 0, tx_count, tx_type)
1724 }
1725
1726 pub fn with_nonce_gaps(
1740 &mut self,
1741 gap_pct: u32,
1742 gap_range: Range<u64>,
1743 rng: &mut impl rand::Rng,
1744 ) {
1745 assert!(gap_pct <= 100, "gap_pct must be between 0 and 100");
1746 assert!(gap_range.start >= 1, "gap_range must have a lower bound of at least one");
1747
1748 let mut prev_nonce = 0;
1749 for tx in &mut self.transactions {
1750 if rng.random_bool(gap_pct as f64 / 100.0) {
1751 prev_nonce += gap_range.start;
1752 } else {
1753 prev_nonce += 1;
1754 }
1755 tx.set_nonce(prev_nonce);
1756 }
1757 }
1758
1759 pub fn extend<T: IntoIterator<Item = MockTransaction>>(&mut self, txs: T) {
1761 self.transactions.extend(txs);
1762 }
1763
1764 pub fn into_vec(self) -> Vec<MockTransaction> {
1766 self.transactions
1767 }
1768
1769 pub fn iter(&self) -> impl Iterator<Item = &MockTransaction> {
1771 self.transactions.iter()
1772 }
1773
1774 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MockTransaction> {
1776 self.transactions.iter_mut()
1777 }
1778}
1779
1780impl IntoIterator for MockTransactionSet {
1781 type Item = MockTransaction;
1782 type IntoIter = IntoIter<MockTransaction>;
1783
1784 fn into_iter(self) -> Self::IntoIter {
1785 self.transactions.into_iter()
1786 }
1787}
1788
1789#[test]
1790fn test_mock_priority() {
1791 use crate::TransactionOrdering;
1792
1793 let o = MockOrdering::default();
1794 let lo = MockTransaction::eip1559().with_gas_limit(100_000);
1795 let hi = lo.next().inc_price();
1796 assert!(o.priority(&hi, 0) > o.priority(&lo, 0));
1797}
1798
1799#[cfg(test)]
1800mod tests {
1801 use super::*;
1802 use alloy_consensus::Transaction;
1803 use alloy_primitives::U256;
1804
1805 #[test]
1806 fn test_mock_transaction_factory() {
1807 let mut factory = MockTransactionFactory::default();
1808
1809 let legacy = factory.create_legacy();
1811 assert_eq!(legacy.transaction.tx_type(), TxType::Legacy);
1812
1813 let eip1559 = factory.create_eip1559();
1815 assert_eq!(eip1559.transaction.tx_type(), TxType::Eip1559);
1816
1817 let eip4844 = factory.create_eip4844();
1819 assert_eq!(eip4844.transaction.tx_type(), TxType::Eip4844);
1820 }
1821
1822 #[test]
1823 fn test_mock_transaction_set() {
1824 let sender = Address::random();
1825 let nonce_start = 0u64;
1826 let count = 3;
1827
1828 let legacy_set = MockTransactionSet::dependent(sender, nonce_start, count, TxType::Legacy);
1830 assert_eq!(legacy_set.transactions.len(), count);
1831 for (idx, tx) in legacy_set.transactions.iter().enumerate() {
1832 assert_eq!(tx.tx_type(), TxType::Legacy);
1833 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1834 assert_eq!(tx.sender(), sender);
1835 }
1836
1837 let eip1559_set =
1839 MockTransactionSet::dependent(sender, nonce_start, count, TxType::Eip1559);
1840 assert_eq!(eip1559_set.transactions.len(), count);
1841 for (idx, tx) in eip1559_set.transactions.iter().enumerate() {
1842 assert_eq!(tx.tx_type(), TxType::Eip1559);
1843 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1844 assert_eq!(tx.sender(), sender);
1845 }
1846 }
1847
1848 #[test]
1849 fn test_mock_transaction_modifications() {
1850 let tx = MockTransaction::eip1559();
1851
1852 let original_price = tx.get_gas_price();
1854 let tx_inc = tx.inc_price();
1855 assert!(tx_inc.get_gas_price() > original_price);
1856
1857 let original_limit = tx.gas_limit();
1859 let tx_inc = tx.inc_limit();
1860 assert!(tx_inc.gas_limit() > original_limit);
1861
1862 let original_nonce = tx.nonce();
1864 let tx_inc = tx.inc_nonce();
1865 assert_eq!(tx_inc.nonce(), original_nonce + 1);
1866 }
1867
1868 #[test]
1869 fn test_mock_transaction_cost() {
1870 let tx = MockTransaction::eip1559()
1871 .with_gas_limit(7_000)
1872 .with_max_fee(100)
1873 .with_value(U256::ZERO);
1874
1875 let expected_cost = U256::from(7_000u64) * U256::from(100u128) + U256::ZERO;
1877 assert_eq!(*tx.cost(), expected_cost);
1878 }
1879}