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 fn with_blob_hashes(mut self, count: usize) -> Self {
433 if let Self::Eip4844 { blob_versioned_hashes, .. } = &mut self {
434 *blob_versioned_hashes = (0..count).map(|_| B256::random()).collect();
435 }
436 self
437 }
438
439 pub const fn set_blob_fee(&mut self, val: u128) -> &mut Self {
441 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = self {
442 *max_fee_per_blob_gas = val;
443 }
444 self
445 }
446
447 pub const fn set_priority_fee(&mut self, val: u128) -> &mut Self {
449 if let Self::Eip1559 { max_priority_fee_per_gas, .. } |
450 Self::Eip4844 { max_priority_fee_per_gas, .. } = self
451 {
452 *max_priority_fee_per_gas = val;
453 }
454 self
455 }
456
457 pub const fn with_priority_fee(mut self, val: u128) -> Self {
459 self.set_priority_fee(val);
460 self
461 }
462
463 pub const fn get_priority_fee(&self) -> Option<u128> {
465 match self {
466 Self::Eip1559 { max_priority_fee_per_gas, .. } |
467 Self::Eip4844 { max_priority_fee_per_gas, .. } |
468 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
469 _ => None,
470 }
471 }
472
473 pub const fn set_max_fee(&mut self, val: u128) -> &mut Self {
475 if let Self::Eip1559 { max_fee_per_gas, .. } |
476 Self::Eip4844 { max_fee_per_gas, .. } |
477 Self::Eip7702 { max_fee_per_gas, .. } = self
478 {
479 *max_fee_per_gas = val;
480 }
481 self
482 }
483
484 pub const fn with_max_fee(mut self, val: u128) -> Self {
486 self.set_max_fee(val);
487 self
488 }
489
490 pub const fn get_max_fee(&self) -> Option<u128> {
492 match self {
493 Self::Eip1559 { max_fee_per_gas, .. } |
494 Self::Eip4844 { max_fee_per_gas, .. } |
495 Self::Eip7702 { max_fee_per_gas, .. } => Some(*max_fee_per_gas),
496 _ => None,
497 }
498 }
499
500 pub fn set_accesslist(&mut self, list: AccessList) -> &mut Self {
502 match self {
503 Self::Legacy { .. } => {}
504 Self::Eip1559 { access_list: accesslist, .. } |
505 Self::Eip4844 { access_list: accesslist, .. } |
506 Self::Eip2930 { access_list: accesslist, .. } |
507 Self::Eip7702 { access_list: accesslist, .. } => {
508 *accesslist = list;
509 }
510 }
511 self
512 }
513
514 pub fn set_authorization_list(&mut self, list: Vec<SignedAuthorization>) -> &mut Self {
516 if let Self::Eip7702 { authorization_list, .. } = self {
517 *authorization_list = list;
518 }
519
520 self
521 }
522
523 pub const fn set_gas_price(&mut self, val: u128) -> &mut Self {
525 match self {
526 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => {
527 *gas_price = val;
528 }
529 Self::Eip1559 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
530 Self::Eip4844 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
531 Self::Eip7702 { max_fee_per_gas, max_priority_fee_per_gas, .. } => {
532 *max_fee_per_gas = val;
533 *max_priority_fee_per_gas = val;
534 }
535 }
536 self
537 }
538
539 pub const fn with_gas_price(mut self, val: u128) -> Self {
541 match self {
542 Self::Legacy { ref mut gas_price, .. } | Self::Eip2930 { ref mut gas_price, .. } => {
543 *gas_price = val;
544 }
545 Self::Eip1559 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
546 Self::Eip4844 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
547 Self::Eip7702 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } => {
548 *max_fee_per_gas = val;
549 *max_priority_fee_per_gas = val;
550 }
551 }
552 self
553 }
554
555 pub const fn get_gas_price(&self) -> u128 {
557 match self {
558 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
559 Self::Eip1559 { max_fee_per_gas, .. } |
560 Self::Eip4844 { max_fee_per_gas, .. } |
561 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
562 }
563 }
564
565 pub fn prev(&self) -> Self {
567 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() - 1)
568 }
569
570 pub fn next(&self) -> Self {
572 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + 1)
573 }
574
575 pub fn skip(&self, skip: u64) -> Self {
577 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + skip + 1)
578 }
579
580 pub fn inc_nonce(self) -> Self {
582 let nonce = self.get_nonce() + 1;
583 self.with_nonce(nonce)
584 }
585
586 pub fn rng_hash(self) -> Self {
588 self.with_hash(B256::random())
589 }
590
591 pub fn inc_price(&self) -> Self {
593 self.inc_price_by(1)
594 }
595
596 pub fn inc_price_by(&self, value: u128) -> Self {
598 self.clone().with_gas_price(self.get_gas_price().checked_add(value).unwrap())
599 }
600
601 pub fn decr_price(&self) -> Self {
603 self.decr_price_by(1)
604 }
605
606 pub fn decr_price_by(&self, value: u128) -> Self {
608 self.clone().with_gas_price(self.get_gas_price().checked_sub(value).unwrap())
609 }
610
611 pub fn inc_value(&self) -> Self {
613 self.clone().with_value(self.get_value().checked_add(U256::from(1)).unwrap())
614 }
615
616 pub fn inc_limit(&self) -> Self {
618 self.clone().with_gas_limit(self.get_gas_limit() + 1)
619 }
620
621 pub fn inc_blob_fee(&self) -> Self {
625 self.inc_blob_fee_by(1)
626 }
627
628 pub fn inc_blob_fee_by(&self, value: u128) -> Self {
632 let mut this = self.clone();
633 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
634 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_add(value).unwrap();
635 }
636 this
637 }
638
639 pub fn decr_blob_fee(&self) -> Self {
643 self.decr_price_by(1)
644 }
645
646 pub fn decr_blob_fee_by(&self, value: u128) -> Self {
650 let mut this = self.clone();
651 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
652 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_sub(value).unwrap();
653 }
654 this
655 }
656
657 pub const fn tx_type(&self) -> u8 {
659 match self {
660 Self::Legacy { .. } => LEGACY_TX_TYPE_ID,
661 Self::Eip1559 { .. } => EIP1559_TX_TYPE_ID,
662 Self::Eip4844 { .. } => EIP4844_TX_TYPE_ID,
663 Self::Eip2930 { .. } => EIP2930_TX_TYPE_ID,
664 Self::Eip7702 { .. } => EIP7702_TX_TYPE_ID,
665 }
666 }
667
668 pub const fn is_legacy(&self) -> bool {
670 matches!(self, Self::Legacy { .. })
671 }
672
673 pub const fn is_eip1559(&self) -> bool {
675 matches!(self, Self::Eip1559 { .. })
676 }
677
678 pub const fn is_eip4844(&self) -> bool {
680 matches!(self, Self::Eip4844 { .. })
681 }
682
683 pub const fn is_eip2930(&self) -> bool {
685 matches!(self, Self::Eip2930 { .. })
686 }
687
688 pub const fn is_eip7702(&self) -> bool {
690 matches!(self, Self::Eip7702 { .. })
691 }
692
693 fn update_cost(&mut self) {
694 match self {
695 Self::Legacy { cost, gas_limit, gas_price, value, .. } |
696 Self::Eip2930 { cost, gas_limit, gas_price, value, .. } => {
697 *cost = U256::from(*gas_limit) * U256::from(*gas_price) + *value
698 }
699 Self::Eip1559 { cost, gas_limit, max_fee_per_gas, value, .. } |
700 Self::Eip4844 { cost, gas_limit, max_fee_per_gas, value, .. } |
701 Self::Eip7702 { cost, gas_limit, max_fee_per_gas, value, .. } => {
702 *cost = U256::from(*gas_limit) * U256::from(*max_fee_per_gas) + *value
703 }
704 };
705 }
706}
707
708impl PoolTransaction for MockTransaction {
709 type TryFromConsensusError = ValueError<EthereumTxEnvelope<TxEip4844>>;
710
711 type Consensus = TransactionSigned;
712
713 type Pooled = PooledTransactionVariant;
714
715 fn consensus_ref(&self) -> Recovered<&Self::Consensus> {
716 unimplemented!("mock transaction does not wrap a consensus transaction")
717 }
718
719 fn into_consensus(self) -> Recovered<Self::Consensus> {
720 self.into()
721 }
722
723 fn from_pooled(pooled: Recovered<Self::Pooled>) -> Self {
724 pooled.into()
725 }
726
727 fn hash(&self) -> &TxHash {
728 self.get_hash()
729 }
730
731 fn sender(&self) -> Address {
732 *self.get_sender()
733 }
734
735 fn sender_ref(&self) -> &Address {
736 self.get_sender()
737 }
738
739 fn cost(&self) -> &U256 {
744 match self {
745 Self::Legacy { cost, .. } |
746 Self::Eip2930 { cost, .. } |
747 Self::Eip1559 { cost, .. } |
748 Self::Eip4844 { cost, .. } |
749 Self::Eip7702 { cost, .. } => cost,
750 }
751 }
752
753 fn encoded_length(&self) -> usize {
755 self.size()
756 }
757}
758
759impl InMemorySize for MockTransaction {
760 fn size(&self) -> usize {
761 *self.get_size()
762 }
763}
764
765impl Typed2718 for MockTransaction {
766 fn ty(&self) -> u8 {
767 match self {
768 Self::Legacy { .. } => TxType::Legacy.into(),
769 Self::Eip1559 { .. } => TxType::Eip1559.into(),
770 Self::Eip4844 { .. } => TxType::Eip4844.into(),
771 Self::Eip2930 { .. } => TxType::Eip2930.into(),
772 Self::Eip7702 { .. } => TxType::Eip7702.into(),
773 }
774 }
775}
776
777impl alloy_consensus::Transaction for MockTransaction {
778 fn chain_id(&self) -> Option<u64> {
779 match self {
780 Self::Legacy { chain_id, .. } => *chain_id,
781 Self::Eip1559 { chain_id, .. } |
782 Self::Eip4844 { chain_id, .. } |
783 Self::Eip2930 { chain_id, .. } |
784 Self::Eip7702 { chain_id, .. } => Some(*chain_id),
785 }
786 }
787
788 fn nonce(&self) -> u64 {
789 *self.get_nonce()
790 }
791
792 fn gas_limit(&self) -> u64 {
793 *self.get_gas_limit()
794 }
795
796 fn gas_price(&self) -> Option<u128> {
797 match self {
798 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => Some(*gas_price),
799 _ => None,
800 }
801 }
802
803 fn max_fee_per_gas(&self) -> u128 {
804 match self {
805 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
806 Self::Eip1559 { max_fee_per_gas, .. } |
807 Self::Eip4844 { max_fee_per_gas, .. } |
808 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
809 }
810 }
811
812 fn max_priority_fee_per_gas(&self) -> Option<u128> {
813 match self {
814 Self::Legacy { .. } | Self::Eip2930 { .. } => None,
815 Self::Eip1559 { max_priority_fee_per_gas, .. } |
816 Self::Eip4844 { max_priority_fee_per_gas, .. } |
817 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
818 }
819 }
820
821 fn max_fee_per_blob_gas(&self) -> Option<u128> {
822 match self {
823 Self::Eip4844 { max_fee_per_blob_gas, .. } => Some(*max_fee_per_blob_gas),
824 _ => None,
825 }
826 }
827
828 fn priority_fee_or_price(&self) -> u128 {
829 match self {
830 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
831 Self::Eip1559 { max_priority_fee_per_gas, .. } |
832 Self::Eip4844 { max_priority_fee_per_gas, .. } |
833 Self::Eip7702 { max_priority_fee_per_gas, .. } => *max_priority_fee_per_gas,
834 }
835 }
836
837 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
838 base_fee.map_or_else(
839 || self.max_fee_per_gas(),
840 |base_fee| {
841 let tip = self.max_fee_per_gas().saturating_sub(base_fee as u128);
844 if let Some(max_tip) = self.max_priority_fee_per_gas() {
845 if tip > max_tip {
846 max_tip + base_fee as u128
847 } else {
848 self.max_fee_per_gas()
850 }
851 } else {
852 self.max_fee_per_gas()
853 }
854 },
855 )
856 }
857
858 fn is_dynamic_fee(&self) -> bool {
859 !matches!(self, Self::Legacy { .. } | Self::Eip2930 { .. })
860 }
861
862 fn kind(&self) -> TxKind {
863 match self {
864 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => *to,
865 Self::Eip4844 { to, .. } | Self::Eip7702 { to, .. } => TxKind::Call(*to),
866 }
867 }
868
869 fn is_create(&self) -> bool {
870 match self {
871 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => {
872 to.is_create()
873 }
874 Self::Eip4844 { .. } | Self::Eip7702 { .. } => false,
875 }
876 }
877
878 fn value(&self) -> U256 {
879 match self {
880 Self::Legacy { value, .. } |
881 Self::Eip1559 { value, .. } |
882 Self::Eip2930 { value, .. } |
883 Self::Eip4844 { value, .. } |
884 Self::Eip7702 { value, .. } => *value,
885 }
886 }
887
888 fn input(&self) -> &Bytes {
889 self.get_input()
890 }
891
892 fn access_list(&self) -> Option<&AccessList> {
893 match self {
894 Self::Legacy { .. } => None,
895 Self::Eip1559 { access_list: accesslist, .. } |
896 Self::Eip4844 { access_list: accesslist, .. } |
897 Self::Eip2930 { access_list: accesslist, .. } |
898 Self::Eip7702 { access_list: accesslist, .. } => Some(accesslist),
899 }
900 }
901
902 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
903 match self {
904 Self::Eip4844 { blob_versioned_hashes, .. } => Some(blob_versioned_hashes),
905 _ => None,
906 }
907 }
908
909 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
910 match self {
911 Self::Eip7702 { authorization_list, .. } => Some(authorization_list),
912 _ => None,
913 }
914 }
915}
916
917impl EthPoolTransaction for MockTransaction {
918 fn take_blob(&mut self) -> EthBlobTransactionSidecar {
919 match self {
920 Self::Eip4844 { sidecar, .. } => EthBlobTransactionSidecar::Present(sidecar.clone()),
921 _ => EthBlobTransactionSidecar::None,
922 }
923 }
924
925 fn try_into_pooled_eip4844(
926 self,
927 sidecar: Arc<BlobTransactionSidecarVariant>,
928 ) -> Option<Recovered<Self::Pooled>> {
929 let (tx, signer) = self.into_consensus().into_parts();
930 tx.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar))
931 .map(|tx| tx.with_signer(signer))
932 .ok()
933 }
934
935 fn try_from_eip4844(
936 tx: Recovered<Self::Consensus>,
937 sidecar: BlobTransactionSidecarVariant,
938 ) -> Option<Self> {
939 let (tx, signer) = tx.into_parts();
940 tx.try_into_pooled_eip4844(sidecar)
941 .map(|tx| tx.with_signer(signer))
942 .ok()
943 .map(Self::from_pooled)
944 }
945
946 fn validate_blob(
947 &self,
948 _blob: &BlobTransactionSidecarVariant,
949 _settings: &KzgSettings,
950 ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> {
951 match &self {
952 Self::Eip4844 { .. } => Ok(()),
953 _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
954 }
955 }
956}
957
958impl TryFrom<Recovered<TransactionSigned>> for MockTransaction {
959 type Error = TryFromRecoveredTransactionError;
960
961 fn try_from(tx: Recovered<TransactionSigned>) -> Result<Self, Self::Error> {
962 let sender = tx.signer();
963 let transaction = tx.into_inner();
964 let hash = *transaction.tx_hash();
965 let size = transaction.size();
966
967 match transaction.into_typed_transaction() {
968 Transaction::Legacy(TxLegacy {
969 chain_id,
970 nonce,
971 gas_price,
972 gas_limit,
973 to,
974 value,
975 input,
976 }) => Ok(Self::Legacy {
977 chain_id,
978 hash,
979 sender,
980 nonce,
981 gas_price,
982 gas_limit,
983 to,
984 value,
985 input,
986 size,
987 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
988 }),
989 Transaction::Eip2930(TxEip2930 {
990 chain_id,
991 nonce,
992 gas_price,
993 gas_limit,
994 to,
995 value,
996 input,
997 access_list,
998 }) => Ok(Self::Eip2930 {
999 chain_id,
1000 hash,
1001 sender,
1002 nonce,
1003 gas_price,
1004 gas_limit,
1005 to,
1006 value,
1007 input,
1008 access_list,
1009 size,
1010 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
1011 }),
1012 Transaction::Eip1559(TxEip1559 {
1013 chain_id,
1014 nonce,
1015 gas_limit,
1016 max_fee_per_gas,
1017 max_priority_fee_per_gas,
1018 to,
1019 value,
1020 input,
1021 access_list,
1022 }) => Ok(Self::Eip1559 {
1023 chain_id,
1024 hash,
1025 sender,
1026 nonce,
1027 max_fee_per_gas,
1028 max_priority_fee_per_gas,
1029 gas_limit,
1030 to,
1031 value,
1032 input,
1033 access_list,
1034 size,
1035 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1036 }),
1037 Transaction::Eip4844(TxEip4844 {
1038 chain_id,
1039 nonce,
1040 gas_limit,
1041 max_fee_per_gas,
1042 max_priority_fee_per_gas,
1043 to,
1044 value,
1045 input,
1046 access_list,
1047 blob_versioned_hashes: _,
1048 max_fee_per_blob_gas,
1049 }) => Ok(Self::Eip4844 {
1050 chain_id,
1051 hash,
1052 sender,
1053 nonce,
1054 max_fee_per_gas,
1055 max_priority_fee_per_gas,
1056 max_fee_per_blob_gas,
1057 gas_limit,
1058 to,
1059 value,
1060 input,
1061 access_list,
1062 sidecar: BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default()),
1063 blob_versioned_hashes: Default::default(),
1064 size,
1065 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1066 }),
1067 Transaction::Eip7702(TxEip7702 {
1068 chain_id,
1069 nonce,
1070 gas_limit,
1071 max_fee_per_gas,
1072 max_priority_fee_per_gas,
1073 to,
1074 value,
1075 access_list,
1076 authorization_list,
1077 input,
1078 }) => Ok(Self::Eip7702 {
1079 chain_id,
1080 hash,
1081 sender,
1082 nonce,
1083 max_fee_per_gas,
1084 max_priority_fee_per_gas,
1085 gas_limit,
1086 to,
1087 value,
1088 input,
1089 access_list,
1090 authorization_list,
1091 size,
1092 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1093 }),
1094 }
1095 }
1096}
1097
1098impl TryFrom<Recovered<EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>>>
1099 for MockTransaction
1100{
1101 type Error = TryFromRecoveredTransactionError;
1102
1103 fn try_from(
1104 tx: Recovered<EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>>,
1105 ) -> Result<Self, Self::Error> {
1106 let sender = tx.signer();
1107 let transaction = tx.into_inner();
1108 let hash = *transaction.tx_hash();
1109 let size = transaction.size();
1110
1111 match transaction {
1112 EthereumTxEnvelope::Legacy(signed_tx) => {
1113 let tx = signed_tx.strip_signature();
1114 Ok(Self::Legacy {
1115 chain_id: tx.chain_id,
1116 hash,
1117 sender,
1118 nonce: tx.nonce,
1119 gas_price: tx.gas_price,
1120 gas_limit: tx.gas_limit,
1121 to: tx.to,
1122 value: tx.value,
1123 input: tx.input,
1124 size,
1125 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1126 })
1127 }
1128 EthereumTxEnvelope::Eip2930(signed_tx) => {
1129 let tx = signed_tx.strip_signature();
1130 Ok(Self::Eip2930 {
1131 chain_id: tx.chain_id,
1132 hash,
1133 sender,
1134 nonce: tx.nonce,
1135 gas_price: tx.gas_price,
1136 gas_limit: tx.gas_limit,
1137 to: tx.to,
1138 value: tx.value,
1139 input: tx.input,
1140 access_list: tx.access_list,
1141 size,
1142 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1143 })
1144 }
1145 EthereumTxEnvelope::Eip1559(signed_tx) => {
1146 let tx = signed_tx.strip_signature();
1147 Ok(Self::Eip1559 {
1148 chain_id: tx.chain_id,
1149 hash,
1150 sender,
1151 nonce: tx.nonce,
1152 max_fee_per_gas: tx.max_fee_per_gas,
1153 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1154 gas_limit: tx.gas_limit,
1155 to: tx.to,
1156 value: tx.value,
1157 input: tx.input,
1158 access_list: tx.access_list,
1159 size,
1160 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1161 })
1162 }
1163 EthereumTxEnvelope::Eip4844(signed_tx) => match signed_tx.tx() {
1164 TxEip4844Variant::TxEip4844(tx) => Ok(Self::Eip4844 {
1165 chain_id: tx.chain_id,
1166 hash,
1167 sender,
1168 nonce: tx.nonce,
1169 max_fee_per_gas: tx.max_fee_per_gas,
1170 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1171 max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
1172 gas_limit: tx.gas_limit,
1173 to: tx.to,
1174 value: tx.value,
1175 input: tx.input.clone(),
1176 access_list: tx.access_list.clone(),
1177 sidecar: BlobTransactionSidecarVariant::Eip4844(
1178 BlobTransactionSidecar::default(),
1179 ),
1180 blob_versioned_hashes: tx.blob_versioned_hashes.clone(),
1181 size,
1182 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1183 }),
1184 tx => Err(TryFromRecoveredTransactionError::UnsupportedTransactionType(tx.ty())),
1185 },
1186 EthereumTxEnvelope::Eip7702(signed_tx) => {
1187 let tx = signed_tx.strip_signature();
1188 Ok(Self::Eip7702 {
1189 chain_id: tx.chain_id,
1190 hash,
1191 sender,
1192 nonce: tx.nonce,
1193 max_fee_per_gas: tx.max_fee_per_gas,
1194 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1195 gas_limit: tx.gas_limit,
1196 to: tx.to,
1197 value: tx.value,
1198 access_list: tx.access_list,
1199 authorization_list: tx.authorization_list,
1200 input: tx.input,
1201 size,
1202 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1203 })
1204 }
1205 }
1206 }
1207}
1208
1209impl From<Recovered<PooledTransactionVariant>> for MockTransaction {
1210 fn from(tx: Recovered<PooledTransactionVariant>) -> Self {
1211 let (tx, signer) = tx.into_parts();
1212 Recovered::<TransactionSigned>::new_unchecked(tx.into(), signer).try_into().expect(
1213 "Failed to convert from PooledTransactionsElementEcRecovered to MockTransaction",
1214 )
1215 }
1216}
1217
1218impl From<MockTransaction> for Recovered<TransactionSigned> {
1219 fn from(tx: MockTransaction) -> Self {
1220 let hash = *tx.hash();
1221 let sender = tx.sender();
1222 let tx = Transaction::from(tx);
1223 let tx: TransactionSigned =
1224 Signed::new_unchecked(tx, Signature::test_signature(), hash).into();
1225 Self::new_unchecked(tx, sender)
1226 }
1227}
1228
1229impl From<MockTransaction> for Transaction {
1230 fn from(mock: MockTransaction) -> Self {
1231 match mock {
1232 MockTransaction::Legacy {
1233 chain_id,
1234 nonce,
1235 gas_price,
1236 gas_limit,
1237 to,
1238 value,
1239 input,
1240 ..
1241 } => Self::Legacy(TxLegacy { chain_id, nonce, gas_price, gas_limit, to, value, input }),
1242 MockTransaction::Eip2930 {
1243 chain_id,
1244 nonce,
1245 gas_price,
1246 gas_limit,
1247 to,
1248 value,
1249 access_list,
1250 input,
1251 ..
1252 } => Self::Eip2930(TxEip2930 {
1253 chain_id,
1254 nonce,
1255 gas_price,
1256 gas_limit,
1257 to,
1258 value,
1259 access_list,
1260 input,
1261 }),
1262 MockTransaction::Eip1559 {
1263 chain_id,
1264 nonce,
1265 gas_limit,
1266 max_fee_per_gas,
1267 max_priority_fee_per_gas,
1268 to,
1269 value,
1270 access_list,
1271 input,
1272 ..
1273 } => Self::Eip1559(TxEip1559 {
1274 chain_id,
1275 nonce,
1276 gas_limit,
1277 max_fee_per_gas,
1278 max_priority_fee_per_gas,
1279 to,
1280 value,
1281 access_list,
1282 input,
1283 }),
1284 MockTransaction::Eip4844 {
1285 chain_id,
1286 nonce,
1287 gas_limit,
1288 max_fee_per_gas,
1289 max_priority_fee_per_gas,
1290 to,
1291 value,
1292 access_list,
1293 sidecar,
1294 max_fee_per_blob_gas,
1295 input,
1296 ..
1297 } => Self::Eip4844(TxEip4844 {
1298 chain_id,
1299 nonce,
1300 gas_limit,
1301 max_fee_per_gas,
1302 max_priority_fee_per_gas,
1303 to,
1304 value,
1305 access_list,
1306 blob_versioned_hashes: sidecar.versioned_hashes().collect(),
1307 max_fee_per_blob_gas,
1308 input,
1309 }),
1310 MockTransaction::Eip7702 {
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 input,
1320 authorization_list,
1321 ..
1322 } => Self::Eip7702(TxEip7702 {
1323 chain_id,
1324 nonce,
1325 gas_limit,
1326 max_fee_per_gas,
1327 max_priority_fee_per_gas,
1328 to,
1329 value,
1330 access_list,
1331 authorization_list,
1332 input,
1333 }),
1334 }
1335 }
1336}
1337
1338#[cfg(any(test, feature = "arbitrary"))]
1339impl proptest::arbitrary::Arbitrary for MockTransaction {
1340 type Parameters = ();
1341 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1342 use proptest::prelude::Strategy;
1343 use proptest_arbitrary_interop::arb;
1344
1345 arb::<(TransactionSigned, Address)>()
1346 .prop_map(|(signed_transaction, signer)| {
1347 Recovered::new_unchecked(signed_transaction, signer)
1348 .try_into()
1349 .expect("Failed to create an Arbitrary MockTransaction from a Recovered tx")
1350 })
1351 .boxed()
1352 }
1353
1354 type Strategy = proptest::strategy::BoxedStrategy<Self>;
1355}
1356
1357#[derive(Debug, Default)]
1359pub struct MockTransactionFactory {
1360 pub(crate) ids: SenderIdentifiers,
1361}
1362
1363impl MockTransactionFactory {
1366 pub fn tx_id(&mut self, tx: &MockTransaction) -> TransactionId {
1368 let sender = self.ids.sender_id_or_create(tx.sender());
1369 TransactionId::new(sender, *tx.get_nonce())
1370 }
1371
1372 pub fn validated(&mut self, transaction: MockTransaction) -> MockValidTx {
1374 self.validated_with_origin(TransactionOrigin::External, transaction)
1375 }
1376
1377 pub fn validated_arc(&mut self, transaction: MockTransaction) -> Arc<MockValidTx> {
1379 Arc::new(self.validated(transaction))
1380 }
1381
1382 pub fn validated_with_origin(
1384 &mut self,
1385 origin: TransactionOrigin,
1386 transaction: MockTransaction,
1387 ) -> MockValidTx {
1388 MockValidTx {
1389 propagate: false,
1390 transaction_id: self.tx_id(&transaction),
1391 transaction,
1392 timestamp: Instant::now(),
1393 origin,
1394 authority_ids: None,
1395 }
1396 }
1397
1398 pub fn create_legacy(&mut self) -> MockValidTx {
1400 self.validated(MockTransaction::legacy())
1401 }
1402
1403 pub fn create_eip1559(&mut self) -> MockValidTx {
1405 self.validated(MockTransaction::eip1559())
1406 }
1407
1408 pub fn create_eip4844(&mut self) -> MockValidTx {
1410 self.validated(MockTransaction::eip4844())
1411 }
1412}
1413
1414pub type MockOrdering = CoinbaseTipOrdering<MockTransaction>;
1416
1417#[derive(Debug, Clone)]
1420pub struct MockTransactionRatio {
1421 pub legacy_pct: u32,
1423 pub access_list_pct: u32,
1425 pub dynamic_fee_pct: u32,
1427 pub blob_pct: u32,
1429}
1430
1431impl MockTransactionRatio {
1432 pub fn new(legacy_pct: u32, access_list_pct: u32, dynamic_fee_pct: u32, blob_pct: u32) -> Self {
1438 let total = legacy_pct + access_list_pct + dynamic_fee_pct + blob_pct;
1439 assert_eq!(
1440 total,
1441 100,
1442 "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}",
1443 );
1444
1445 Self { legacy_pct, access_list_pct, dynamic_fee_pct, blob_pct }
1446 }
1447
1448 pub fn weighted_index(&self) -> WeightedIndex<u32> {
1456 WeightedIndex::new([
1457 self.legacy_pct,
1458 self.access_list_pct,
1459 self.dynamic_fee_pct,
1460 self.blob_pct,
1461 ])
1462 .unwrap()
1463 }
1464}
1465
1466#[derive(Debug, Clone)]
1468pub struct MockFeeRange {
1469 pub gas_price: Uniform<u128>,
1471 pub priority_fee: Uniform<u128>,
1473 pub max_fee: Uniform<u128>,
1475 pub max_fee_blob: Uniform<u128>,
1477}
1478
1479impl MockFeeRange {
1480 pub fn new(
1485 gas_price: Range<u128>,
1486 priority_fee: Range<u128>,
1487 max_fee: Range<u128>,
1488 max_fee_blob: Range<u128>,
1489 ) -> Self {
1490 assert!(
1491 max_fee.start >= priority_fee.end,
1492 "max_fee_range should be strictly above the priority fee range"
1493 );
1494 Self {
1495 gas_price: gas_price.try_into().unwrap(),
1496 priority_fee: priority_fee.try_into().unwrap(),
1497 max_fee: max_fee.try_into().unwrap(),
1498 max_fee_blob: max_fee_blob.try_into().unwrap(),
1499 }
1500 }
1501
1502 pub fn sample_gas_price(&self, rng: &mut impl rand::Rng) -> u128 {
1505 self.gas_price.sample(rng)
1506 }
1507
1508 pub fn sample_priority_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1511 self.priority_fee.sample(rng)
1512 }
1513
1514 pub fn sample_max_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1517 self.max_fee.sample(rng)
1518 }
1519
1520 pub fn sample_max_fee_blob(&self, rng: &mut impl rand::Rng) -> u128 {
1523 self.max_fee_blob.sample(rng)
1524 }
1525}
1526
1527#[derive(Debug, Clone)]
1529pub struct MockTransactionDistribution {
1530 transaction_ratio: MockTransactionRatio,
1532 gas_limit_range: Uniform<u64>,
1534 size_range: Uniform<usize>,
1536 fee_ranges: MockFeeRange,
1538}
1539
1540impl MockTransactionDistribution {
1541 pub fn new(
1543 transaction_ratio: MockTransactionRatio,
1544 fee_ranges: MockFeeRange,
1545 gas_limit_range: Range<u64>,
1546 size_range: Range<usize>,
1547 ) -> Self {
1548 Self {
1549 transaction_ratio,
1550 gas_limit_range: gas_limit_range.try_into().unwrap(),
1551 fee_ranges,
1552 size_range: size_range.try_into().unwrap(),
1553 }
1554 }
1555
1556 pub fn tx(&self, nonce: u64, rng: &mut impl rand::Rng) -> MockTransaction {
1558 let transaction_sample = self.transaction_ratio.weighted_index().sample(rng);
1559 let tx = match transaction_sample {
1560 0 => MockTransaction::legacy().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1561 1 => MockTransaction::eip2930().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1562 2 => MockTransaction::eip1559()
1563 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1564 .with_max_fee(self.fee_ranges.sample_max_fee(rng)),
1565 3 => MockTransaction::eip4844()
1566 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1567 .with_max_fee(self.fee_ranges.sample_max_fee(rng))
1568 .with_blob_fee(self.fee_ranges.sample_max_fee_blob(rng)),
1569 _ => unreachable!("unknown transaction type returned by the weighted index"),
1570 };
1571
1572 let size = self.size_range.sample(rng);
1573
1574 tx.with_nonce(nonce).with_gas_limit(self.gas_limit_range.sample(rng)).with_size(size)
1575 }
1576
1577 pub fn tx_set(
1581 &self,
1582 sender: Address,
1583 nonce_range: Range<u64>,
1584 rng: &mut impl rand::Rng,
1585 ) -> MockTransactionSet {
1586 let txs =
1587 nonce_range.map(|nonce| self.tx(nonce, rng).with_sender(sender)).collect::<Vec<_>>();
1588 MockTransactionSet::new(txs)
1589 }
1590
1591 pub fn tx_set_non_conflicting_types(
1597 &self,
1598 sender: Address,
1599 nonce_range: Range<u64>,
1600 rng: &mut impl rand::Rng,
1601 ) -> NonConflictingSetOutcome {
1602 let mut modified_distribution = self.clone();
1610 let first_tx = self.tx(nonce_range.start, rng);
1611
1612 if first_tx.is_eip4844() {
1615 modified_distribution.transaction_ratio = MockTransactionRatio {
1616 legacy_pct: 0,
1617 access_list_pct: 0,
1618 dynamic_fee_pct: 0,
1619 blob_pct: 100,
1620 };
1621
1622 NonConflictingSetOutcome::BlobsOnly(modified_distribution.tx_set(
1624 sender,
1625 nonce_range,
1626 rng,
1627 ))
1628 } else {
1629 let MockTransactionRatio { legacy_pct, access_list_pct, dynamic_fee_pct, .. } =
1630 modified_distribution.transaction_ratio;
1631
1632 let total_non_blob_weight: u32 = legacy_pct + access_list_pct + dynamic_fee_pct;
1634
1635 let new_weights: Vec<u32> = [legacy_pct, access_list_pct, dynamic_fee_pct]
1637 .into_iter()
1638 .map(|weight| weight * 100 / total_non_blob_weight)
1639 .collect();
1640
1641 let new_ratio = MockTransactionRatio {
1642 legacy_pct: new_weights[0],
1643 access_list_pct: new_weights[1],
1644 dynamic_fee_pct: new_weights[2],
1645 blob_pct: 0,
1646 };
1647
1648 modified_distribution.transaction_ratio = new_ratio;
1651
1652 NonConflictingSetOutcome::Mixed(modified_distribution.tx_set(sender, nonce_range, rng))
1654 }
1655 }
1656}
1657
1658#[derive(Debug, Clone)]
1661pub enum NonConflictingSetOutcome {
1662 BlobsOnly(MockTransactionSet),
1664 Mixed(MockTransactionSet),
1666}
1667
1668impl NonConflictingSetOutcome {
1669 pub fn into_inner(self) -> MockTransactionSet {
1671 match self {
1672 Self::BlobsOnly(set) | Self::Mixed(set) => set,
1673 }
1674 }
1675
1676 pub fn with_nonce_gaps(
1684 &mut self,
1685 gap_pct: u32,
1686 gap_range: Range<u64>,
1687 rng: &mut impl rand::Rng,
1688 ) {
1689 match self {
1690 Self::BlobsOnly(_) => {}
1691 Self::Mixed(set) => set.with_nonce_gaps(gap_pct, gap_range, rng),
1692 }
1693 }
1694}
1695
1696#[derive(Debug, Clone)]
1698pub struct MockTransactionSet {
1699 pub(crate) transactions: Vec<MockTransaction>,
1700}
1701
1702impl MockTransactionSet {
1703 const fn new(transactions: Vec<MockTransaction>) -> Self {
1705 Self { transactions }
1706 }
1707
1708 pub fn dependent(sender: Address, from_nonce: u64, tx_count: usize, tx_type: TxType) -> Self {
1715 let mut txs = Vec::with_capacity(tx_count);
1716 let mut curr_tx =
1717 MockTransaction::new_from_type(tx_type).with_nonce(from_nonce).with_sender(sender);
1718 for _ in 0..tx_count {
1719 txs.push(curr_tx.clone());
1720 curr_tx = curr_tx.next();
1721 }
1722
1723 Self::new(txs)
1724 }
1725
1726 pub fn sequential_transactions_by_sender(
1731 sender: Address,
1732 tx_count: usize,
1733 tx_type: TxType,
1734 ) -> Self {
1735 Self::dependent(sender, 0, tx_count, tx_type)
1736 }
1737
1738 pub fn with_nonce_gaps(
1752 &mut self,
1753 gap_pct: u32,
1754 gap_range: Range<u64>,
1755 rng: &mut impl rand::Rng,
1756 ) {
1757 assert!(gap_pct <= 100, "gap_pct must be between 0 and 100");
1758 assert!(gap_range.start >= 1, "gap_range must have a lower bound of at least one");
1759
1760 let mut prev_nonce = 0;
1761 for tx in &mut self.transactions {
1762 if rng.random_bool(gap_pct as f64 / 100.0) {
1763 prev_nonce += gap_range.start;
1764 } else {
1765 prev_nonce += 1;
1766 }
1767 tx.set_nonce(prev_nonce);
1768 }
1769 }
1770
1771 pub fn extend<T: IntoIterator<Item = MockTransaction>>(&mut self, txs: T) {
1773 self.transactions.extend(txs);
1774 }
1775
1776 pub fn into_vec(self) -> Vec<MockTransaction> {
1778 self.transactions
1779 }
1780
1781 pub fn iter(&self) -> impl Iterator<Item = &MockTransaction> {
1783 self.transactions.iter()
1784 }
1785
1786 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MockTransaction> {
1788 self.transactions.iter_mut()
1789 }
1790}
1791
1792impl IntoIterator for MockTransactionSet {
1793 type Item = MockTransaction;
1794 type IntoIter = IntoIter<MockTransaction>;
1795
1796 fn into_iter(self) -> Self::IntoIter {
1797 self.transactions.into_iter()
1798 }
1799}
1800
1801#[test]
1802fn test_mock_priority() {
1803 use crate::TransactionOrdering;
1804
1805 let o = MockOrdering::default();
1806 let lo = MockTransaction::eip1559().with_gas_limit(100_000);
1807 let hi = lo.next().inc_price();
1808 assert!(o.priority(&hi, 0) > o.priority(&lo, 0));
1809}
1810
1811#[cfg(test)]
1812mod tests {
1813 use super::*;
1814 use alloy_consensus::Transaction;
1815 use alloy_primitives::U256;
1816
1817 #[test]
1818 fn test_mock_transaction_factory() {
1819 let mut factory = MockTransactionFactory::default();
1820
1821 let legacy = factory.create_legacy();
1823 assert_eq!(legacy.transaction.tx_type(), TxType::Legacy);
1824
1825 let eip1559 = factory.create_eip1559();
1827 assert_eq!(eip1559.transaction.tx_type(), TxType::Eip1559);
1828
1829 let eip4844 = factory.create_eip4844();
1831 assert_eq!(eip4844.transaction.tx_type(), TxType::Eip4844);
1832 }
1833
1834 #[test]
1835 fn test_mock_transaction_set() {
1836 let sender = Address::random();
1837 let nonce_start = 0u64;
1838 let count = 3;
1839
1840 let legacy_set = MockTransactionSet::dependent(sender, nonce_start, count, TxType::Legacy);
1842 assert_eq!(legacy_set.transactions.len(), count);
1843 for (idx, tx) in legacy_set.transactions.iter().enumerate() {
1844 assert_eq!(tx.tx_type(), TxType::Legacy);
1845 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1846 assert_eq!(tx.sender(), sender);
1847 }
1848
1849 let eip1559_set =
1851 MockTransactionSet::dependent(sender, nonce_start, count, TxType::Eip1559);
1852 assert_eq!(eip1559_set.transactions.len(), count);
1853 for (idx, tx) in eip1559_set.transactions.iter().enumerate() {
1854 assert_eq!(tx.tx_type(), TxType::Eip1559);
1855 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1856 assert_eq!(tx.sender(), sender);
1857 }
1858 }
1859
1860 #[test]
1861 fn test_mock_transaction_modifications() {
1862 let tx = MockTransaction::eip1559();
1863
1864 let original_price = tx.get_gas_price();
1866 let tx_inc = tx.inc_price();
1867 assert!(tx_inc.get_gas_price() > original_price);
1868
1869 let original_limit = tx.gas_limit();
1871 let tx_inc = tx.inc_limit();
1872 assert!(tx_inc.gas_limit() > original_limit);
1873
1874 let original_nonce = tx.nonce();
1876 let tx_inc = tx.inc_nonce();
1877 assert_eq!(tx_inc.nonce(), original_nonce + 1);
1878 }
1879
1880 #[test]
1881 fn test_mock_transaction_cost() {
1882 let tx = MockTransaction::eip1559()
1883 .with_gas_limit(7_000)
1884 .with_max_fee(100)
1885 .with_value(U256::ZERO);
1886
1887 let expected_cost = U256::from(7_000u64) * U256::from(100u128) + U256::ZERO;
1889 assert_eq!(*tx.cost(), expected_cost);
1890 }
1891}