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, .. } => {
921 EthBlobTransactionSidecar::Present(sidecar.clone().into())
922 }
923 _ => EthBlobTransactionSidecar::None,
924 }
925 }
926
927 fn try_into_pooled_eip4844(
928 self,
929 sidecar: Arc<BlobTransactionSidecarVariant>,
930 ) -> Option<Recovered<Self::Pooled>> {
931 let (tx, signer) = self.into_consensus().into_parts();
932 tx.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar))
933 .map(|tx| tx.with_signer(signer))
934 .ok()
935 }
936
937 fn try_from_eip4844(
938 tx: Recovered<Self::Consensus>,
939 sidecar: BlobTransactionSidecarVariant,
940 ) -> Option<Self> {
941 let (tx, signer) = tx.into_parts();
942 tx.try_into_pooled_eip4844(sidecar)
943 .map(|tx| tx.with_signer(signer))
944 .ok()
945 .map(Self::from_pooled)
946 }
947
948 fn validate_blob(
949 &self,
950 _blob: &BlobTransactionSidecarVariant,
951 _settings: &KzgSettings,
952 ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> {
953 match &self {
954 Self::Eip4844 { .. } => Ok(()),
955 _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
956 }
957 }
958}
959
960impl TryFrom<Recovered<TransactionSigned>> for MockTransaction {
961 type Error = TryFromRecoveredTransactionError;
962
963 fn try_from(tx: Recovered<TransactionSigned>) -> Result<Self, Self::Error> {
964 let sender = tx.signer();
965 let transaction = tx.into_inner();
966 let hash = *transaction.tx_hash();
967 let size = transaction.size();
968
969 match transaction.into_typed_transaction() {
970 Transaction::Legacy(TxLegacy {
971 chain_id,
972 nonce,
973 gas_price,
974 gas_limit,
975 to,
976 value,
977 input,
978 }) => Ok(Self::Legacy {
979 chain_id,
980 hash,
981 sender,
982 nonce,
983 gas_price,
984 gas_limit,
985 to,
986 value,
987 input,
988 size,
989 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
990 }),
991 Transaction::Eip2930(TxEip2930 {
992 chain_id,
993 nonce,
994 gas_price,
995 gas_limit,
996 to,
997 value,
998 input,
999 access_list,
1000 }) => Ok(Self::Eip2930 {
1001 chain_id,
1002 hash,
1003 sender,
1004 nonce,
1005 gas_price,
1006 gas_limit,
1007 to,
1008 value,
1009 input,
1010 access_list,
1011 size,
1012 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
1013 }),
1014 Transaction::Eip1559(TxEip1559 {
1015 chain_id,
1016 nonce,
1017 gas_limit,
1018 max_fee_per_gas,
1019 max_priority_fee_per_gas,
1020 to,
1021 value,
1022 input,
1023 access_list,
1024 }) => Ok(Self::Eip1559 {
1025 chain_id,
1026 hash,
1027 sender,
1028 nonce,
1029 max_fee_per_gas,
1030 max_priority_fee_per_gas,
1031 gas_limit,
1032 to,
1033 value,
1034 input,
1035 access_list,
1036 size,
1037 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1038 }),
1039 Transaction::Eip4844(TxEip4844 {
1040 chain_id,
1041 nonce,
1042 gas_limit,
1043 max_fee_per_gas,
1044 max_priority_fee_per_gas,
1045 to,
1046 value,
1047 input,
1048 access_list,
1049 blob_versioned_hashes: _,
1050 max_fee_per_blob_gas,
1051 }) => Ok(Self::Eip4844 {
1052 chain_id,
1053 hash,
1054 sender,
1055 nonce,
1056 max_fee_per_gas,
1057 max_priority_fee_per_gas,
1058 max_fee_per_blob_gas,
1059 gas_limit,
1060 to,
1061 value,
1062 input,
1063 access_list,
1064 sidecar: BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default()),
1065 blob_versioned_hashes: Default::default(),
1066 size,
1067 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1068 }),
1069 Transaction::Eip7702(TxEip7702 {
1070 chain_id,
1071 nonce,
1072 gas_limit,
1073 max_fee_per_gas,
1074 max_priority_fee_per_gas,
1075 to,
1076 value,
1077 access_list,
1078 authorization_list,
1079 input,
1080 }) => Ok(Self::Eip7702 {
1081 chain_id,
1082 hash,
1083 sender,
1084 nonce,
1085 max_fee_per_gas,
1086 max_priority_fee_per_gas,
1087 gas_limit,
1088 to,
1089 value,
1090 input,
1091 access_list,
1092 authorization_list,
1093 size,
1094 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1095 }),
1096 }
1097 }
1098}
1099
1100impl TryFrom<Recovered<EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>>>
1101 for MockTransaction
1102{
1103 type Error = TryFromRecoveredTransactionError;
1104
1105 fn try_from(
1106 tx: Recovered<EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>>,
1107 ) -> Result<Self, Self::Error> {
1108 let sender = tx.signer();
1109 let transaction = tx.into_inner();
1110 let hash = *transaction.tx_hash();
1111 let size = transaction.size();
1112
1113 match transaction {
1114 EthereumTxEnvelope::Legacy(signed_tx) => {
1115 let tx = signed_tx.strip_signature();
1116 Ok(Self::Legacy {
1117 chain_id: tx.chain_id,
1118 hash,
1119 sender,
1120 nonce: tx.nonce,
1121 gas_price: tx.gas_price,
1122 gas_limit: tx.gas_limit,
1123 to: tx.to,
1124 value: tx.value,
1125 input: tx.input,
1126 size,
1127 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1128 })
1129 }
1130 EthereumTxEnvelope::Eip2930(signed_tx) => {
1131 let tx = signed_tx.strip_signature();
1132 Ok(Self::Eip2930 {
1133 chain_id: tx.chain_id,
1134 hash,
1135 sender,
1136 nonce: tx.nonce,
1137 gas_price: tx.gas_price,
1138 gas_limit: tx.gas_limit,
1139 to: tx.to,
1140 value: tx.value,
1141 input: tx.input,
1142 access_list: tx.access_list,
1143 size,
1144 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1145 })
1146 }
1147 EthereumTxEnvelope::Eip1559(signed_tx) => {
1148 let tx = signed_tx.strip_signature();
1149 Ok(Self::Eip1559 {
1150 chain_id: tx.chain_id,
1151 hash,
1152 sender,
1153 nonce: tx.nonce,
1154 max_fee_per_gas: tx.max_fee_per_gas,
1155 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1156 gas_limit: tx.gas_limit,
1157 to: tx.to,
1158 value: tx.value,
1159 input: tx.input,
1160 access_list: tx.access_list,
1161 size,
1162 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1163 })
1164 }
1165 EthereumTxEnvelope::Eip4844(signed_tx) => match signed_tx.tx() {
1166 TxEip4844Variant::TxEip4844(tx) => Ok(Self::Eip4844 {
1167 chain_id: tx.chain_id,
1168 hash,
1169 sender,
1170 nonce: tx.nonce,
1171 max_fee_per_gas: tx.max_fee_per_gas,
1172 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1173 max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
1174 gas_limit: tx.gas_limit,
1175 to: tx.to,
1176 value: tx.value,
1177 input: tx.input.clone(),
1178 access_list: tx.access_list.clone(),
1179 sidecar: BlobTransactionSidecarVariant::Eip4844(
1180 BlobTransactionSidecar::default(),
1181 ),
1182 blob_versioned_hashes: tx.blob_versioned_hashes.clone(),
1183 size,
1184 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1185 }),
1186 tx => Err(TryFromRecoveredTransactionError::UnsupportedTransactionType(tx.ty())),
1187 },
1188 EthereumTxEnvelope::Eip7702(signed_tx) => {
1189 let tx = signed_tx.strip_signature();
1190 Ok(Self::Eip7702 {
1191 chain_id: tx.chain_id,
1192 hash,
1193 sender,
1194 nonce: tx.nonce,
1195 max_fee_per_gas: tx.max_fee_per_gas,
1196 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1197 gas_limit: tx.gas_limit,
1198 to: tx.to,
1199 value: tx.value,
1200 access_list: tx.access_list,
1201 authorization_list: tx.authorization_list,
1202 input: tx.input,
1203 size,
1204 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1205 })
1206 }
1207 }
1208 }
1209}
1210
1211impl From<Recovered<PooledTransactionVariant>> for MockTransaction {
1212 fn from(tx: Recovered<PooledTransactionVariant>) -> Self {
1213 let (tx, signer) = tx.into_parts();
1214 Recovered::<TransactionSigned>::new_unchecked(tx.into(), signer).try_into().expect(
1215 "Failed to convert from PooledTransactionsElementEcRecovered to MockTransaction",
1216 )
1217 }
1218}
1219
1220impl From<MockTransaction> for Recovered<TransactionSigned> {
1221 fn from(tx: MockTransaction) -> Self {
1222 let hash = *tx.hash();
1223 let sender = tx.sender();
1224 let tx = Transaction::from(tx);
1225 let tx: TransactionSigned =
1226 Signed::new_unchecked(tx, Signature::test_signature(), hash).into();
1227 Self::new_unchecked(tx, sender)
1228 }
1229}
1230
1231impl From<MockTransaction> for Transaction {
1232 fn from(mock: MockTransaction) -> Self {
1233 match mock {
1234 MockTransaction::Legacy {
1235 chain_id,
1236 nonce,
1237 gas_price,
1238 gas_limit,
1239 to,
1240 value,
1241 input,
1242 ..
1243 } => Self::Legacy(TxLegacy { chain_id, nonce, gas_price, gas_limit, to, value, input }),
1244 MockTransaction::Eip2930 {
1245 chain_id,
1246 nonce,
1247 gas_price,
1248 gas_limit,
1249 to,
1250 value,
1251 access_list,
1252 input,
1253 ..
1254 } => Self::Eip2930(TxEip2930 {
1255 chain_id,
1256 nonce,
1257 gas_price,
1258 gas_limit,
1259 to,
1260 value,
1261 access_list,
1262 input,
1263 }),
1264 MockTransaction::Eip1559 {
1265 chain_id,
1266 nonce,
1267 gas_limit,
1268 max_fee_per_gas,
1269 max_priority_fee_per_gas,
1270 to,
1271 value,
1272 access_list,
1273 input,
1274 ..
1275 } => Self::Eip1559(TxEip1559 {
1276 chain_id,
1277 nonce,
1278 gas_limit,
1279 max_fee_per_gas,
1280 max_priority_fee_per_gas,
1281 to,
1282 value,
1283 access_list,
1284 input,
1285 }),
1286 MockTransaction::Eip4844 {
1287 chain_id,
1288 nonce,
1289 gas_limit,
1290 max_fee_per_gas,
1291 max_priority_fee_per_gas,
1292 to,
1293 value,
1294 access_list,
1295 sidecar,
1296 max_fee_per_blob_gas,
1297 input,
1298 ..
1299 } => Self::Eip4844(TxEip4844 {
1300 chain_id,
1301 nonce,
1302 gas_limit,
1303 max_fee_per_gas,
1304 max_priority_fee_per_gas,
1305 to,
1306 value,
1307 access_list,
1308 blob_versioned_hashes: sidecar.versioned_hashes().collect(),
1309 max_fee_per_blob_gas,
1310 input,
1311 }),
1312 MockTransaction::Eip7702 {
1313 chain_id,
1314 nonce,
1315 gas_limit,
1316 max_fee_per_gas,
1317 max_priority_fee_per_gas,
1318 to,
1319 value,
1320 access_list,
1321 input,
1322 authorization_list,
1323 ..
1324 } => Self::Eip7702(TxEip7702 {
1325 chain_id,
1326 nonce,
1327 gas_limit,
1328 max_fee_per_gas,
1329 max_priority_fee_per_gas,
1330 to,
1331 value,
1332 access_list,
1333 authorization_list,
1334 input,
1335 }),
1336 }
1337 }
1338}
1339
1340#[cfg(any(test, feature = "arbitrary"))]
1341impl proptest::arbitrary::Arbitrary for MockTransaction {
1342 type Parameters = ();
1343 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1344 use proptest::prelude::Strategy;
1345 use proptest_arbitrary_interop::arb;
1346
1347 arb::<(TransactionSigned, Address)>()
1348 .prop_map(|(signed_transaction, signer)| {
1349 Recovered::new_unchecked(signed_transaction, signer)
1350 .try_into()
1351 .expect("Failed to create an Arbitrary MockTransaction from a Recovered tx")
1352 })
1353 .boxed()
1354 }
1355
1356 type Strategy = proptest::strategy::BoxedStrategy<Self>;
1357}
1358
1359#[derive(Debug, Default)]
1361pub struct MockTransactionFactory {
1362 pub(crate) ids: SenderIdentifiers,
1363}
1364
1365impl MockTransactionFactory {
1368 pub fn tx_id(&mut self, tx: &MockTransaction) -> TransactionId {
1370 let sender = self.ids.sender_id_or_create(tx.sender());
1371 TransactionId::new(sender, *tx.get_nonce())
1372 }
1373
1374 pub fn validated(&mut self, transaction: MockTransaction) -> MockValidTx {
1376 self.validated_with_origin(TransactionOrigin::External, transaction)
1377 }
1378
1379 pub fn validated_arc(&mut self, transaction: MockTransaction) -> Arc<MockValidTx> {
1381 Arc::new(self.validated(transaction))
1382 }
1383
1384 pub fn validated_with_origin(
1386 &mut self,
1387 origin: TransactionOrigin,
1388 transaction: MockTransaction,
1389 ) -> MockValidTx {
1390 MockValidTx {
1391 propagate: false,
1392 transaction_id: self.tx_id(&transaction),
1393 transaction,
1394 timestamp: Instant::now(),
1395 origin,
1396 authority_ids: None,
1397 }
1398 }
1399
1400 pub fn create_legacy(&mut self) -> MockValidTx {
1402 self.validated(MockTransaction::legacy())
1403 }
1404
1405 pub fn create_eip1559(&mut self) -> MockValidTx {
1407 self.validated(MockTransaction::eip1559())
1408 }
1409
1410 pub fn create_eip4844(&mut self) -> MockValidTx {
1412 self.validated(MockTransaction::eip4844())
1413 }
1414}
1415
1416pub type MockOrdering = CoinbaseTipOrdering<MockTransaction>;
1418
1419#[derive(Debug, Clone)]
1422pub struct MockTransactionRatio {
1423 pub legacy_pct: u32,
1425 pub access_list_pct: u32,
1427 pub dynamic_fee_pct: u32,
1429 pub blob_pct: u32,
1431}
1432
1433impl MockTransactionRatio {
1434 pub fn new(legacy_pct: u32, access_list_pct: u32, dynamic_fee_pct: u32, blob_pct: u32) -> Self {
1440 let total = legacy_pct + access_list_pct + dynamic_fee_pct + blob_pct;
1441 assert_eq!(
1442 total,
1443 100,
1444 "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}",
1445 );
1446
1447 Self { legacy_pct, access_list_pct, dynamic_fee_pct, blob_pct }
1448 }
1449
1450 pub fn weighted_index(&self) -> WeightedIndex<u32> {
1458 WeightedIndex::new([
1459 self.legacy_pct,
1460 self.access_list_pct,
1461 self.dynamic_fee_pct,
1462 self.blob_pct,
1463 ])
1464 .unwrap()
1465 }
1466}
1467
1468#[derive(Debug, Clone)]
1470pub struct MockFeeRange {
1471 pub gas_price: Uniform<u128>,
1473 pub priority_fee: Uniform<u128>,
1475 pub max_fee: Uniform<u128>,
1477 pub max_fee_blob: Uniform<u128>,
1479}
1480
1481impl MockFeeRange {
1482 pub fn new(
1487 gas_price: Range<u128>,
1488 priority_fee: Range<u128>,
1489 max_fee: Range<u128>,
1490 max_fee_blob: Range<u128>,
1491 ) -> Self {
1492 assert!(
1493 max_fee.start >= priority_fee.end,
1494 "max_fee_range should be strictly above the priority fee range"
1495 );
1496 Self {
1497 gas_price: gas_price.try_into().unwrap(),
1498 priority_fee: priority_fee.try_into().unwrap(),
1499 max_fee: max_fee.try_into().unwrap(),
1500 max_fee_blob: max_fee_blob.try_into().unwrap(),
1501 }
1502 }
1503
1504 pub fn sample_gas_price(&self, rng: &mut impl rand::Rng) -> u128 {
1507 self.gas_price.sample(rng)
1508 }
1509
1510 pub fn sample_priority_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1513 self.priority_fee.sample(rng)
1514 }
1515
1516 pub fn sample_max_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1519 self.max_fee.sample(rng)
1520 }
1521
1522 pub fn sample_max_fee_blob(&self, rng: &mut impl rand::Rng) -> u128 {
1525 self.max_fee_blob.sample(rng)
1526 }
1527}
1528
1529#[derive(Debug, Clone)]
1531pub struct MockTransactionDistribution {
1532 transaction_ratio: MockTransactionRatio,
1534 gas_limit_range: Uniform<u64>,
1536 size_range: Uniform<usize>,
1538 fee_ranges: MockFeeRange,
1540}
1541
1542impl MockTransactionDistribution {
1543 pub fn new(
1545 transaction_ratio: MockTransactionRatio,
1546 fee_ranges: MockFeeRange,
1547 gas_limit_range: Range<u64>,
1548 size_range: Range<usize>,
1549 ) -> Self {
1550 Self {
1551 transaction_ratio,
1552 gas_limit_range: gas_limit_range.try_into().unwrap(),
1553 fee_ranges,
1554 size_range: size_range.try_into().unwrap(),
1555 }
1556 }
1557
1558 pub fn tx(&self, nonce: u64, rng: &mut impl rand::Rng) -> MockTransaction {
1560 let transaction_sample = self.transaction_ratio.weighted_index().sample(rng);
1561 let tx = match transaction_sample {
1562 0 => MockTransaction::legacy().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1563 1 => MockTransaction::eip2930().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1564 2 => MockTransaction::eip1559()
1565 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1566 .with_max_fee(self.fee_ranges.sample_max_fee(rng)),
1567 3 => MockTransaction::eip4844()
1568 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1569 .with_max_fee(self.fee_ranges.sample_max_fee(rng))
1570 .with_blob_fee(self.fee_ranges.sample_max_fee_blob(rng)),
1571 _ => unreachable!("unknown transaction type returned by the weighted index"),
1572 };
1573
1574 let size = self.size_range.sample(rng);
1575
1576 tx.with_nonce(nonce).with_gas_limit(self.gas_limit_range.sample(rng)).with_size(size)
1577 }
1578
1579 pub fn tx_set(
1583 &self,
1584 sender: Address,
1585 nonce_range: Range<u64>,
1586 rng: &mut impl rand::Rng,
1587 ) -> MockTransactionSet {
1588 let txs =
1589 nonce_range.map(|nonce| self.tx(nonce, rng).with_sender(sender)).collect::<Vec<_>>();
1590 MockTransactionSet::new(txs)
1591 }
1592
1593 pub fn tx_set_non_conflicting_types(
1599 &self,
1600 sender: Address,
1601 nonce_range: Range<u64>,
1602 rng: &mut impl rand::Rng,
1603 ) -> NonConflictingSetOutcome {
1604 let mut modified_distribution = self.clone();
1612 let first_tx = self.tx(nonce_range.start, rng);
1613
1614 if first_tx.is_eip4844() {
1617 modified_distribution.transaction_ratio = MockTransactionRatio {
1618 legacy_pct: 0,
1619 access_list_pct: 0,
1620 dynamic_fee_pct: 0,
1621 blob_pct: 100,
1622 };
1623
1624 NonConflictingSetOutcome::BlobsOnly(modified_distribution.tx_set(
1626 sender,
1627 nonce_range,
1628 rng,
1629 ))
1630 } else {
1631 let MockTransactionRatio { legacy_pct, access_list_pct, dynamic_fee_pct, .. } =
1632 modified_distribution.transaction_ratio;
1633
1634 let total_non_blob_weight: u32 = legacy_pct + access_list_pct + dynamic_fee_pct;
1636
1637 let new_weights: Vec<u32> = [legacy_pct, access_list_pct, dynamic_fee_pct]
1639 .into_iter()
1640 .map(|weight| weight * 100 / total_non_blob_weight)
1641 .collect();
1642
1643 let new_ratio = MockTransactionRatio {
1644 legacy_pct: new_weights[0],
1645 access_list_pct: new_weights[1],
1646 dynamic_fee_pct: new_weights[2],
1647 blob_pct: 0,
1648 };
1649
1650 modified_distribution.transaction_ratio = new_ratio;
1653
1654 NonConflictingSetOutcome::Mixed(modified_distribution.tx_set(sender, nonce_range, rng))
1656 }
1657 }
1658}
1659
1660#[derive(Debug, Clone)]
1663pub enum NonConflictingSetOutcome {
1664 BlobsOnly(MockTransactionSet),
1666 Mixed(MockTransactionSet),
1668}
1669
1670impl NonConflictingSetOutcome {
1671 pub fn into_inner(self) -> MockTransactionSet {
1673 match self {
1674 Self::BlobsOnly(set) | Self::Mixed(set) => set,
1675 }
1676 }
1677
1678 pub fn with_nonce_gaps(
1686 &mut self,
1687 gap_pct: u32,
1688 gap_range: Range<u64>,
1689 rng: &mut impl rand::Rng,
1690 ) {
1691 match self {
1692 Self::BlobsOnly(_) => {}
1693 Self::Mixed(set) => set.with_nonce_gaps(gap_pct, gap_range, rng),
1694 }
1695 }
1696}
1697
1698#[derive(Debug, Clone)]
1700pub struct MockTransactionSet {
1701 pub(crate) transactions: Vec<MockTransaction>,
1702}
1703
1704impl MockTransactionSet {
1705 const fn new(transactions: Vec<MockTransaction>) -> Self {
1707 Self { transactions }
1708 }
1709
1710 pub fn dependent(sender: Address, from_nonce: u64, tx_count: usize, tx_type: TxType) -> Self {
1717 let mut txs = Vec::with_capacity(tx_count);
1718 let mut curr_tx =
1719 MockTransaction::new_from_type(tx_type).with_nonce(from_nonce).with_sender(sender);
1720 for _ in 0..tx_count {
1721 txs.push(curr_tx.clone());
1722 curr_tx = curr_tx.next();
1723 }
1724
1725 Self::new(txs)
1726 }
1727
1728 pub fn sequential_transactions_by_sender(
1733 sender: Address,
1734 tx_count: usize,
1735 tx_type: TxType,
1736 ) -> Self {
1737 Self::dependent(sender, 0, tx_count, tx_type)
1738 }
1739
1740 pub fn with_nonce_gaps(
1754 &mut self,
1755 gap_pct: u32,
1756 gap_range: Range<u64>,
1757 rng: &mut impl rand::Rng,
1758 ) {
1759 assert!(gap_pct <= 100, "gap_pct must be between 0 and 100");
1760 assert!(gap_range.start >= 1, "gap_range must have a lower bound of at least one");
1761
1762 let mut prev_nonce = 0;
1763 for tx in &mut self.transactions {
1764 if rng.random_bool(gap_pct as f64 / 100.0) {
1765 prev_nonce += gap_range.start;
1766 } else {
1767 prev_nonce += 1;
1768 }
1769 tx.set_nonce(prev_nonce);
1770 }
1771 }
1772
1773 pub fn extend<T: IntoIterator<Item = MockTransaction>>(&mut self, txs: T) {
1775 self.transactions.extend(txs);
1776 }
1777
1778 pub fn into_vec(self) -> Vec<MockTransaction> {
1780 self.transactions
1781 }
1782
1783 pub fn iter(&self) -> impl Iterator<Item = &MockTransaction> {
1785 self.transactions.iter()
1786 }
1787
1788 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MockTransaction> {
1790 self.transactions.iter_mut()
1791 }
1792}
1793
1794impl IntoIterator for MockTransactionSet {
1795 type Item = MockTransaction;
1796 type IntoIter = IntoIter<MockTransaction>;
1797
1798 fn into_iter(self) -> Self::IntoIter {
1799 self.transactions.into_iter()
1800 }
1801}
1802
1803#[test]
1804fn test_mock_priority() {
1805 use crate::TransactionOrdering;
1806
1807 let o = MockOrdering::default();
1808 let lo = MockTransaction::eip1559().with_gas_limit(100_000);
1809 let hi = lo.next().inc_price();
1810 assert!(o.priority(&hi, 0) > o.priority(&lo, 0));
1811}
1812
1813#[cfg(test)]
1814mod tests {
1815 use super::*;
1816 use alloy_consensus::Transaction;
1817 use alloy_primitives::U256;
1818
1819 #[test]
1820 fn test_mock_transaction_factory() {
1821 let mut factory = MockTransactionFactory::default();
1822
1823 let legacy = factory.create_legacy();
1825 assert_eq!(legacy.transaction.tx_type(), TxType::Legacy);
1826
1827 let eip1559 = factory.create_eip1559();
1829 assert_eq!(eip1559.transaction.tx_type(), TxType::Eip1559);
1830
1831 let eip4844 = factory.create_eip4844();
1833 assert_eq!(eip4844.transaction.tx_type(), TxType::Eip4844);
1834 }
1835
1836 #[test]
1837 fn test_mock_transaction_set() {
1838 let sender = Address::random();
1839 let nonce_start = 0u64;
1840 let count = 3;
1841
1842 let legacy_set = MockTransactionSet::dependent(sender, nonce_start, count, TxType::Legacy);
1844 assert_eq!(legacy_set.transactions.len(), count);
1845 for (idx, tx) in legacy_set.transactions.iter().enumerate() {
1846 assert_eq!(tx.tx_type(), TxType::Legacy);
1847 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1848 assert_eq!(tx.sender(), sender);
1849 }
1850
1851 let eip1559_set =
1853 MockTransactionSet::dependent(sender, nonce_start, count, TxType::Eip1559);
1854 assert_eq!(eip1559_set.transactions.len(), count);
1855 for (idx, tx) in eip1559_set.transactions.iter().enumerate() {
1856 assert_eq!(tx.tx_type(), TxType::Eip1559);
1857 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1858 assert_eq!(tx.sender(), sender);
1859 }
1860 }
1861
1862 #[test]
1863 fn test_mock_transaction_modifications() {
1864 let tx = MockTransaction::eip1559();
1865
1866 let original_price = tx.get_gas_price();
1868 let tx_inc = tx.inc_price();
1869 assert!(tx_inc.get_gas_price() > original_price);
1870
1871 let original_limit = tx.gas_limit();
1873 let tx_inc = tx.inc_limit();
1874 assert!(tx_inc.gas_limit() > original_limit);
1875
1876 let original_nonce = tx.nonce();
1878 let tx_inc = tx.inc_nonce();
1879 assert_eq!(tx_inc.nonce(), original_nonce + 1);
1880 }
1881
1882 #[test]
1883 fn test_mock_transaction_cost() {
1884 let tx = MockTransaction::eip1559()
1885 .with_gas_limit(7_000)
1886 .with_max_fee(100)
1887 .with_value(U256::ZERO);
1888
1889 let expected_cost = U256::from(7_000u64) * U256::from(100u128) + U256::ZERO;
1891 assert_eq!(*tx.cost(), expected_cost);
1892 }
1893}