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 ($this:ident => $field:ident) => {
58 let new_value = $field;
59 match $this {
60 MockTransaction::Legacy { ref mut $field, .. } |
61 MockTransaction::Eip1559 { ref mut $field, .. } |
62 MockTransaction::Eip4844 { ref mut $field, .. } |
63 MockTransaction::Eip2930 { ref mut $field, .. } |
64 MockTransaction::Eip7702 { ref mut $field, .. } => {
65 *$field = new_value;
66 }
67 }
68 $this.update_cost();
70 };
71}
72
73macro_rules! get_value {
75 ($this:tt => $field:ident) => {
76 match $this {
77 MockTransaction::Legacy { $field, .. } |
78 MockTransaction::Eip1559 { $field, .. } |
79 MockTransaction::Eip4844 { $field, .. } |
80 MockTransaction::Eip2930 { $field, .. } |
81 MockTransaction::Eip7702 { $field, .. } => $field,
82 }
83 };
84}
85
86macro_rules! make_setters_getters {
88 ($($name:ident => $t:ty);*) => {
89 paste! {$(
90 pub fn [<set_ $name>](&mut self, $name: $t) -> &mut Self {
92 set_value!(self => $name);
93 self
94 }
95
96 pub fn [<with_ $name>](mut self, $name: $t) -> Self {
98 set_value!(self => $name);
99 self
100 }
101
102 pub const fn [<get_ $name>](&self) -> &$t {
104 get_value!(self => $name)
105 }
106 )*}
107 };
108}
109
110#[derive(Debug, Clone, Eq, PartialEq)]
112pub enum MockTransaction {
113 Legacy {
115 chain_id: Option<ChainId>,
117 hash: B256,
119 sender: Address,
121 nonce: u64,
123 gas_price: u128,
125 gas_limit: u64,
127 to: TxKind,
129 value: U256,
131 input: Bytes,
133 size: usize,
135 cost: U256,
137 },
138 Eip2930 {
140 chain_id: ChainId,
142 hash: B256,
144 sender: Address,
146 nonce: u64,
148 to: TxKind,
150 gas_limit: u64,
152 input: Bytes,
154 value: U256,
156 gas_price: u128,
158 access_list: AccessList,
160 size: usize,
162 cost: U256,
164 },
165 Eip1559 {
167 chain_id: ChainId,
169 hash: B256,
171 sender: Address,
173 nonce: u64,
175 max_fee_per_gas: u128,
177 max_priority_fee_per_gas: u128,
179 gas_limit: u64,
181 to: TxKind,
183 value: U256,
185 access_list: AccessList,
187 input: Bytes,
189 size: usize,
191 cost: U256,
193 },
194 Eip4844 {
196 chain_id: ChainId,
198 hash: B256,
200 sender: Address,
202 nonce: u64,
204 max_fee_per_gas: u128,
206 max_priority_fee_per_gas: u128,
208 max_fee_per_blob_gas: u128,
210 gas_limit: u64,
212 to: Address,
214 value: U256,
216 access_list: AccessList,
218 input: Bytes,
220 sidecar: BlobTransactionSidecarVariant,
222 blob_versioned_hashes: Vec<B256>,
224 size: usize,
226 cost: U256,
228 },
229 Eip7702 {
231 chain_id: ChainId,
233 hash: B256,
235 sender: Address,
237 nonce: u64,
239 max_fee_per_gas: u128,
241 max_priority_fee_per_gas: u128,
243 gas_limit: u64,
245 to: Address,
247 value: U256,
249 access_list: AccessList,
251 authorization_list: Vec<SignedAuthorization>,
253 input: Bytes,
255 size: usize,
257 cost: U256,
259 },
260}
261
262impl MockTransaction {
265 make_setters_getters! {
266 nonce => u64;
267 hash => B256;
268 sender => Address;
269 gas_limit => u64;
270 value => U256;
271 input => Bytes;
272 size => usize
273 }
274
275 pub fn legacy() -> Self {
277 Self::Legacy {
278 chain_id: Some(1),
279 hash: B256::random(),
280 sender: Address::random(),
281 nonce: 0,
282 gas_price: 0,
283 gas_limit: 0,
284 to: Address::random().into(),
285 value: Default::default(),
286 input: Default::default(),
287 size: Default::default(),
288 cost: U256::ZERO,
289 }
290 }
291
292 pub fn eip2930() -> Self {
294 Self::Eip2930 {
295 chain_id: 1,
296 hash: B256::random(),
297 sender: Address::random(),
298 nonce: 0,
299 to: Address::random().into(),
300 gas_limit: 0,
301 input: Bytes::new(),
302 value: Default::default(),
303 gas_price: 0,
304 access_list: Default::default(),
305 size: Default::default(),
306 cost: U256::ZERO,
307 }
308 }
309
310 pub fn eip1559() -> Self {
312 Self::Eip1559 {
313 chain_id: 1,
314 hash: B256::random(),
315 sender: Address::random(),
316 nonce: 0,
317 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
318 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
319 gas_limit: 0,
320 to: Address::random().into(),
321 value: Default::default(),
322 input: Bytes::new(),
323 access_list: Default::default(),
324 size: Default::default(),
325 cost: U256::ZERO,
326 }
327 }
328
329 pub fn eip7702() -> Self {
331 Self::Eip7702 {
332 chain_id: 1,
333 hash: B256::random(),
334 sender: Address::random(),
335 nonce: 0,
336 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
337 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
338 gas_limit: 0,
339 to: Address::random(),
340 value: Default::default(),
341 input: Bytes::new(),
342 access_list: Default::default(),
343 authorization_list: vec![],
344 size: Default::default(),
345 cost: U256::ZERO,
346 }
347 }
348
349 pub fn eip4844() -> Self {
351 Self::Eip4844 {
352 chain_id: 1,
353 hash: B256::random(),
354 sender: Address::random(),
355 nonce: 0,
356 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
357 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
358 max_fee_per_blob_gas: DATA_GAS_PER_BLOB as u128,
359 gas_limit: 0,
360 to: Address::random(),
361 value: Default::default(),
362 input: Bytes::new(),
363 access_list: Default::default(),
364 sidecar: BlobTransactionSidecarVariant::Eip4844(Default::default()),
365 blob_versioned_hashes: Default::default(),
366 size: Default::default(),
367 cost: U256::ZERO,
368 }
369 }
370
371 pub fn eip4844_with_sidecar(sidecar: BlobTransactionSidecarVariant) -> Self {
373 let mut transaction = Self::eip4844();
374 if let Self::Eip4844 { sidecar: existing_sidecar, blob_versioned_hashes, .. } =
375 &mut transaction
376 {
377 *blob_versioned_hashes = sidecar.versioned_hashes().collect();
378 *existing_sidecar = sidecar;
379 }
380 transaction
381 }
382
383 pub fn new_from_type(tx_type: TxType) -> Self {
392 #[expect(unreachable_patterns)]
393 match tx_type {
394 TxType::Legacy => Self::legacy(),
395 TxType::Eip2930 => Self::eip2930(),
396 TxType::Eip1559 => Self::eip1559(),
397 TxType::Eip4844 => Self::eip4844(),
398 TxType::Eip7702 => Self::eip7702(),
399
400 _ => unreachable!("Invalid transaction type"),
401 }
402 }
403
404 pub const fn with_blob_fee(mut self, val: u128) -> Self {
406 self.set_blob_fee(val);
407 self
408 }
409
410 pub const fn set_blob_fee(&mut self, val: u128) -> &mut Self {
412 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = self {
413 *max_fee_per_blob_gas = val;
414 }
415 self
416 }
417
418 pub const fn set_priority_fee(&mut self, val: u128) -> &mut Self {
420 if let Self::Eip1559 { max_priority_fee_per_gas, .. } |
421 Self::Eip4844 { max_priority_fee_per_gas, .. } = self
422 {
423 *max_priority_fee_per_gas = val;
424 }
425 self
426 }
427
428 pub const fn with_priority_fee(mut self, val: u128) -> Self {
430 self.set_priority_fee(val);
431 self
432 }
433
434 pub const fn get_priority_fee(&self) -> Option<u128> {
436 match self {
437 Self::Eip1559 { max_priority_fee_per_gas, .. } |
438 Self::Eip4844 { max_priority_fee_per_gas, .. } |
439 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
440 _ => None,
441 }
442 }
443
444 pub const fn set_max_fee(&mut self, val: u128) -> &mut Self {
446 if let Self::Eip1559 { max_fee_per_gas, .. } |
447 Self::Eip4844 { max_fee_per_gas, .. } |
448 Self::Eip7702 { max_fee_per_gas, .. } = self
449 {
450 *max_fee_per_gas = val;
451 }
452 self
453 }
454
455 pub const fn with_max_fee(mut self, val: u128) -> Self {
457 self.set_max_fee(val);
458 self
459 }
460
461 pub const fn get_max_fee(&self) -> Option<u128> {
463 match self {
464 Self::Eip1559 { max_fee_per_gas, .. } |
465 Self::Eip4844 { max_fee_per_gas, .. } |
466 Self::Eip7702 { max_fee_per_gas, .. } => Some(*max_fee_per_gas),
467 _ => None,
468 }
469 }
470
471 pub fn set_accesslist(&mut self, list: AccessList) -> &mut Self {
473 match self {
474 Self::Legacy { .. } => {}
475 Self::Eip1559 { access_list: accesslist, .. } |
476 Self::Eip4844 { access_list: accesslist, .. } |
477 Self::Eip2930 { access_list: accesslist, .. } |
478 Self::Eip7702 { access_list: accesslist, .. } => {
479 *accesslist = list;
480 }
481 }
482 self
483 }
484
485 pub fn set_authorization_list(&mut self, list: Vec<SignedAuthorization>) -> &mut Self {
487 if let Self::Eip7702 { authorization_list, .. } = self {
488 *authorization_list = list;
489 }
490
491 self
492 }
493
494 pub const fn set_gas_price(&mut self, val: u128) -> &mut Self {
496 match self {
497 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => {
498 *gas_price = val;
499 }
500 Self::Eip1559 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
501 Self::Eip4844 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
502 Self::Eip7702 { max_fee_per_gas, max_priority_fee_per_gas, .. } => {
503 *max_fee_per_gas = val;
504 *max_priority_fee_per_gas = val;
505 }
506 }
507 self
508 }
509
510 pub const fn with_gas_price(mut self, val: u128) -> Self {
512 match self {
513 Self::Legacy { ref mut gas_price, .. } | Self::Eip2930 { ref mut gas_price, .. } => {
514 *gas_price = val;
515 }
516 Self::Eip1559 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
517 Self::Eip4844 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
518 Self::Eip7702 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } => {
519 *max_fee_per_gas = val;
520 *max_priority_fee_per_gas = val;
521 }
522 }
523 self
524 }
525
526 pub const fn get_gas_price(&self) -> u128 {
528 match self {
529 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
530 Self::Eip1559 { max_fee_per_gas, .. } |
531 Self::Eip4844 { max_fee_per_gas, .. } |
532 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
533 }
534 }
535
536 pub fn prev(&self) -> Self {
538 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() - 1)
539 }
540
541 pub fn next(&self) -> Self {
543 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + 1)
544 }
545
546 pub fn skip(&self, skip: u64) -> Self {
548 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + skip + 1)
549 }
550
551 pub fn inc_nonce(self) -> Self {
553 let nonce = self.get_nonce() + 1;
554 self.with_nonce(nonce)
555 }
556
557 pub fn rng_hash(self) -> Self {
559 self.with_hash(B256::random())
560 }
561
562 pub fn inc_price(&self) -> Self {
564 self.inc_price_by(1)
565 }
566
567 pub fn inc_price_by(&self, value: u128) -> Self {
569 self.clone().with_gas_price(self.get_gas_price().checked_add(value).unwrap())
570 }
571
572 pub fn decr_price(&self) -> Self {
574 self.decr_price_by(1)
575 }
576
577 pub fn decr_price_by(&self, value: u128) -> Self {
579 self.clone().with_gas_price(self.get_gas_price().checked_sub(value).unwrap())
580 }
581
582 pub fn inc_value(&self) -> Self {
584 self.clone().with_value(self.get_value().checked_add(U256::from(1)).unwrap())
585 }
586
587 pub fn inc_limit(&self) -> Self {
589 self.clone().with_gas_limit(self.get_gas_limit() + 1)
590 }
591
592 pub fn inc_blob_fee(&self) -> Self {
596 self.inc_blob_fee_by(1)
597 }
598
599 pub fn inc_blob_fee_by(&self, value: u128) -> Self {
603 let mut this = self.clone();
604 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
605 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_add(value).unwrap();
606 }
607 this
608 }
609
610 pub fn decr_blob_fee(&self) -> Self {
614 self.decr_price_by(1)
615 }
616
617 pub fn decr_blob_fee_by(&self, value: u128) -> Self {
621 let mut this = self.clone();
622 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
623 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_sub(value).unwrap();
624 }
625 this
626 }
627
628 pub const fn tx_type(&self) -> u8 {
630 match self {
631 Self::Legacy { .. } => LEGACY_TX_TYPE_ID,
632 Self::Eip1559 { .. } => EIP1559_TX_TYPE_ID,
633 Self::Eip4844 { .. } => EIP4844_TX_TYPE_ID,
634 Self::Eip2930 { .. } => EIP2930_TX_TYPE_ID,
635 Self::Eip7702 { .. } => EIP7702_TX_TYPE_ID,
636 }
637 }
638
639 pub const fn is_legacy(&self) -> bool {
641 matches!(self, Self::Legacy { .. })
642 }
643
644 pub const fn is_eip1559(&self) -> bool {
646 matches!(self, Self::Eip1559 { .. })
647 }
648
649 pub const fn is_eip4844(&self) -> bool {
651 matches!(self, Self::Eip4844 { .. })
652 }
653
654 pub const fn is_eip2930(&self) -> bool {
656 matches!(self, Self::Eip2930 { .. })
657 }
658
659 pub const fn is_eip7702(&self) -> bool {
661 matches!(self, Self::Eip7702 { .. })
662 }
663
664 fn update_cost(&mut self) {
665 match self {
666 Self::Legacy { cost, gas_limit, gas_price, value, .. } |
667 Self::Eip2930 { cost, gas_limit, gas_price, value, .. } => {
668 *cost = U256::from(*gas_limit) * U256::from(*gas_price) + *value
669 }
670 Self::Eip1559 { cost, gas_limit, max_fee_per_gas, value, .. } |
671 Self::Eip4844 { cost, gas_limit, max_fee_per_gas, value, .. } |
672 Self::Eip7702 { cost, gas_limit, max_fee_per_gas, value, .. } => {
673 *cost = U256::from(*gas_limit) * U256::from(*max_fee_per_gas) + *value
674 }
675 };
676 }
677}
678
679impl PoolTransaction for MockTransaction {
680 type TryFromConsensusError = ValueError<EthereumTxEnvelope<TxEip4844>>;
681
682 type Consensus = TransactionSigned;
683
684 type Pooled = PooledTransactionVariant;
685
686 fn into_consensus(self) -> Recovered<Self::Consensus> {
687 self.into()
688 }
689
690 fn from_pooled(pooled: Recovered<Self::Pooled>) -> Self {
691 pooled.into()
692 }
693
694 fn hash(&self) -> &TxHash {
695 self.get_hash()
696 }
697
698 fn sender(&self) -> Address {
699 *self.get_sender()
700 }
701
702 fn sender_ref(&self) -> &Address {
703 self.get_sender()
704 }
705
706 fn cost(&self) -> &U256 {
711 match self {
712 Self::Legacy { cost, .. } |
713 Self::Eip2930 { cost, .. } |
714 Self::Eip1559 { cost, .. } |
715 Self::Eip4844 { cost, .. } |
716 Self::Eip7702 { cost, .. } => cost,
717 }
718 }
719
720 fn encoded_length(&self) -> usize {
722 self.size()
723 }
724}
725
726impl InMemorySize for MockTransaction {
727 fn size(&self) -> usize {
728 *self.get_size()
729 }
730}
731
732impl Typed2718 for MockTransaction {
733 fn ty(&self) -> u8 {
734 match self {
735 Self::Legacy { .. } => TxType::Legacy.into(),
736 Self::Eip1559 { .. } => TxType::Eip1559.into(),
737 Self::Eip4844 { .. } => TxType::Eip4844.into(),
738 Self::Eip2930 { .. } => TxType::Eip2930.into(),
739 Self::Eip7702 { .. } => TxType::Eip7702.into(),
740 }
741 }
742}
743
744impl alloy_consensus::Transaction for MockTransaction {
745 fn chain_id(&self) -> Option<u64> {
746 match self {
747 Self::Legacy { chain_id, .. } => *chain_id,
748 Self::Eip1559 { chain_id, .. } |
749 Self::Eip4844 { chain_id, .. } |
750 Self::Eip2930 { chain_id, .. } |
751 Self::Eip7702 { chain_id, .. } => Some(*chain_id),
752 }
753 }
754
755 fn nonce(&self) -> u64 {
756 *self.get_nonce()
757 }
758
759 fn gas_limit(&self) -> u64 {
760 *self.get_gas_limit()
761 }
762
763 fn gas_price(&self) -> Option<u128> {
764 match self {
765 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => Some(*gas_price),
766 _ => None,
767 }
768 }
769
770 fn max_fee_per_gas(&self) -> u128 {
771 match self {
772 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
773 Self::Eip1559 { max_fee_per_gas, .. } |
774 Self::Eip4844 { max_fee_per_gas, .. } |
775 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
776 }
777 }
778
779 fn max_priority_fee_per_gas(&self) -> Option<u128> {
780 match self {
781 Self::Legacy { .. } | Self::Eip2930 { .. } => None,
782 Self::Eip1559 { max_priority_fee_per_gas, .. } |
783 Self::Eip4844 { max_priority_fee_per_gas, .. } |
784 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
785 }
786 }
787
788 fn max_fee_per_blob_gas(&self) -> Option<u128> {
789 match self {
790 Self::Eip4844 { max_fee_per_blob_gas, .. } => Some(*max_fee_per_blob_gas),
791 _ => None,
792 }
793 }
794
795 fn priority_fee_or_price(&self) -> u128 {
796 match self {
797 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
798 Self::Eip1559 { max_priority_fee_per_gas, .. } |
799 Self::Eip4844 { max_priority_fee_per_gas, .. } |
800 Self::Eip7702 { max_priority_fee_per_gas, .. } => *max_priority_fee_per_gas,
801 }
802 }
803
804 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
805 base_fee.map_or(self.max_fee_per_gas(), |base_fee| {
806 let tip = self.max_fee_per_gas().saturating_sub(base_fee as u128);
809 if let Some(max_tip) = self.max_priority_fee_per_gas() {
810 if tip > max_tip {
811 max_tip + base_fee as u128
812 } else {
813 self.max_fee_per_gas()
815 }
816 } else {
817 self.max_fee_per_gas()
818 }
819 })
820 }
821
822 fn is_dynamic_fee(&self) -> bool {
823 !matches!(self, Self::Legacy { .. } | Self::Eip2930 { .. })
824 }
825
826 fn kind(&self) -> TxKind {
827 match self {
828 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => *to,
829 Self::Eip4844 { to, .. } | Self::Eip7702 { to, .. } => TxKind::Call(*to),
830 }
831 }
832
833 fn is_create(&self) -> bool {
834 match self {
835 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => {
836 to.is_create()
837 }
838 Self::Eip4844 { .. } | Self::Eip7702 { .. } => false,
839 }
840 }
841
842 fn value(&self) -> U256 {
843 match self {
844 Self::Legacy { value, .. } |
845 Self::Eip1559 { value, .. } |
846 Self::Eip2930 { value, .. } |
847 Self::Eip4844 { value, .. } |
848 Self::Eip7702 { value, .. } => *value,
849 }
850 }
851
852 fn input(&self) -> &Bytes {
853 self.get_input()
854 }
855
856 fn access_list(&self) -> Option<&AccessList> {
857 match self {
858 Self::Legacy { .. } => None,
859 Self::Eip1559 { access_list: accesslist, .. } |
860 Self::Eip4844 { access_list: accesslist, .. } |
861 Self::Eip2930 { access_list: accesslist, .. } |
862 Self::Eip7702 { access_list: accesslist, .. } => Some(accesslist),
863 }
864 }
865
866 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
867 match self {
868 Self::Eip4844 { blob_versioned_hashes, .. } => Some(blob_versioned_hashes),
869 _ => None,
870 }
871 }
872
873 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
874 match self {
875 Self::Eip7702 { authorization_list, .. } => Some(authorization_list),
876 _ => None,
877 }
878 }
879}
880
881impl EthPoolTransaction for MockTransaction {
882 fn take_blob(&mut self) -> EthBlobTransactionSidecar {
883 match self {
884 Self::Eip4844 { sidecar, .. } => EthBlobTransactionSidecar::Present(sidecar.clone()),
885 _ => EthBlobTransactionSidecar::None,
886 }
887 }
888
889 fn try_into_pooled_eip4844(
890 self,
891 sidecar: Arc<BlobTransactionSidecarVariant>,
892 ) -> Option<Recovered<Self::Pooled>> {
893 let (tx, signer) = self.into_consensus().into_parts();
894 tx.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar))
895 .map(|tx| tx.with_signer(signer))
896 .ok()
897 }
898
899 fn try_from_eip4844(
900 tx: Recovered<Self::Consensus>,
901 sidecar: BlobTransactionSidecarVariant,
902 ) -> Option<Self> {
903 let (tx, signer) = tx.into_parts();
904 tx.try_into_pooled_eip4844(sidecar)
905 .map(|tx| tx.with_signer(signer))
906 .ok()
907 .map(Self::from_pooled)
908 }
909
910 fn validate_blob(
911 &self,
912 _blob: &BlobTransactionSidecarVariant,
913 _settings: &KzgSettings,
914 ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> {
915 match &self {
916 Self::Eip4844 { .. } => Ok(()),
917 _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
918 }
919 }
920}
921
922impl TryFrom<Recovered<TransactionSigned>> for MockTransaction {
923 type Error = TryFromRecoveredTransactionError;
924
925 fn try_from(tx: Recovered<TransactionSigned>) -> Result<Self, Self::Error> {
926 let sender = tx.signer();
927 let transaction = tx.into_inner();
928 let hash = *transaction.tx_hash();
929 let size = transaction.size();
930
931 match transaction.into_typed_transaction() {
932 Transaction::Legacy(TxLegacy {
933 chain_id,
934 nonce,
935 gas_price,
936 gas_limit,
937 to,
938 value,
939 input,
940 }) => Ok(Self::Legacy {
941 chain_id,
942 hash,
943 sender,
944 nonce,
945 gas_price,
946 gas_limit,
947 to,
948 value,
949 input,
950 size,
951 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
952 }),
953 Transaction::Eip2930(TxEip2930 {
954 chain_id,
955 nonce,
956 gas_price,
957 gas_limit,
958 to,
959 value,
960 input,
961 access_list,
962 }) => Ok(Self::Eip2930 {
963 chain_id,
964 hash,
965 sender,
966 nonce,
967 gas_price,
968 gas_limit,
969 to,
970 value,
971 input,
972 access_list,
973 size,
974 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
975 }),
976 Transaction::Eip1559(TxEip1559 {
977 chain_id,
978 nonce,
979 gas_limit,
980 max_fee_per_gas,
981 max_priority_fee_per_gas,
982 to,
983 value,
984 input,
985 access_list,
986 }) => Ok(Self::Eip1559 {
987 chain_id,
988 hash,
989 sender,
990 nonce,
991 max_fee_per_gas,
992 max_priority_fee_per_gas,
993 gas_limit,
994 to,
995 value,
996 input,
997 access_list,
998 size,
999 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1000 }),
1001 Transaction::Eip4844(TxEip4844 {
1002 chain_id,
1003 nonce,
1004 gas_limit,
1005 max_fee_per_gas,
1006 max_priority_fee_per_gas,
1007 to,
1008 value,
1009 input,
1010 access_list,
1011 blob_versioned_hashes: _,
1012 max_fee_per_blob_gas,
1013 }) => Ok(Self::Eip4844 {
1014 chain_id,
1015 hash,
1016 sender,
1017 nonce,
1018 max_fee_per_gas,
1019 max_priority_fee_per_gas,
1020 max_fee_per_blob_gas,
1021 gas_limit,
1022 to,
1023 value,
1024 input,
1025 access_list,
1026 sidecar: BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default()),
1027 blob_versioned_hashes: Default::default(),
1028 size,
1029 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1030 }),
1031 Transaction::Eip7702(TxEip7702 {
1032 chain_id,
1033 nonce,
1034 gas_limit,
1035 max_fee_per_gas,
1036 max_priority_fee_per_gas,
1037 to,
1038 value,
1039 access_list,
1040 authorization_list,
1041 input,
1042 }) => Ok(Self::Eip7702 {
1043 chain_id,
1044 hash,
1045 sender,
1046 nonce,
1047 max_fee_per_gas,
1048 max_priority_fee_per_gas,
1049 gas_limit,
1050 to,
1051 value,
1052 input,
1053 access_list,
1054 authorization_list,
1055 size,
1056 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1057 }),
1058 }
1059 }
1060}
1061
1062impl TryFrom<Recovered<EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>>>
1063 for MockTransaction
1064{
1065 type Error = TryFromRecoveredTransactionError;
1066
1067 fn try_from(
1068 tx: Recovered<EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>>,
1069 ) -> Result<Self, Self::Error> {
1070 let sender = tx.signer();
1071 let transaction = tx.into_inner();
1072 let hash = *transaction.tx_hash();
1073 let size = transaction.size();
1074
1075 match transaction {
1076 EthereumTxEnvelope::Legacy(signed_tx) => {
1077 let tx = signed_tx.strip_signature();
1078 Ok(Self::Legacy {
1079 chain_id: tx.chain_id,
1080 hash,
1081 sender,
1082 nonce: tx.nonce,
1083 gas_price: tx.gas_price,
1084 gas_limit: tx.gas_limit,
1085 to: tx.to,
1086 value: tx.value,
1087 input: tx.input,
1088 size,
1089 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1090 })
1091 }
1092 EthereumTxEnvelope::Eip2930(signed_tx) => {
1093 let tx = signed_tx.strip_signature();
1094 Ok(Self::Eip2930 {
1095 chain_id: tx.chain_id,
1096 hash,
1097 sender,
1098 nonce: tx.nonce,
1099 gas_price: tx.gas_price,
1100 gas_limit: tx.gas_limit,
1101 to: tx.to,
1102 value: tx.value,
1103 input: tx.input,
1104 access_list: tx.access_list,
1105 size,
1106 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1107 })
1108 }
1109 EthereumTxEnvelope::Eip1559(signed_tx) => {
1110 let tx = signed_tx.strip_signature();
1111 Ok(Self::Eip1559 {
1112 chain_id: tx.chain_id,
1113 hash,
1114 sender,
1115 nonce: tx.nonce,
1116 max_fee_per_gas: tx.max_fee_per_gas,
1117 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1118 gas_limit: tx.gas_limit,
1119 to: tx.to,
1120 value: tx.value,
1121 input: tx.input,
1122 access_list: tx.access_list,
1123 size,
1124 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1125 })
1126 }
1127 EthereumTxEnvelope::Eip4844(signed_tx) => match signed_tx.tx() {
1128 TxEip4844Variant::TxEip4844(tx) => Ok(Self::Eip4844 {
1129 chain_id: tx.chain_id,
1130 hash,
1131 sender,
1132 nonce: tx.nonce,
1133 max_fee_per_gas: tx.max_fee_per_gas,
1134 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1135 max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
1136 gas_limit: tx.gas_limit,
1137 to: tx.to,
1138 value: tx.value,
1139 input: tx.input.clone(),
1140 access_list: tx.access_list.clone(),
1141 sidecar: BlobTransactionSidecarVariant::Eip4844(
1142 BlobTransactionSidecar::default(),
1143 ),
1144 blob_versioned_hashes: tx.blob_versioned_hashes.clone(),
1145 size,
1146 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1147 }),
1148 tx => Err(TryFromRecoveredTransactionError::UnsupportedTransactionType(tx.ty())),
1149 },
1150 EthereumTxEnvelope::Eip7702(signed_tx) => {
1151 let tx = signed_tx.strip_signature();
1152 Ok(Self::Eip7702 {
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 gas_limit: tx.gas_limit,
1160 to: tx.to,
1161 value: tx.value,
1162 access_list: tx.access_list,
1163 authorization_list: tx.authorization_list,
1164 input: tx.input,
1165 size,
1166 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1167 })
1168 }
1169 }
1170 }
1171}
1172
1173impl From<Recovered<PooledTransactionVariant>> for MockTransaction {
1174 fn from(tx: Recovered<PooledTransactionVariant>) -> Self {
1175 let (tx, signer) = tx.into_parts();
1176 Recovered::<TransactionSigned>::new_unchecked(tx.into(), signer).try_into().expect(
1177 "Failed to convert from PooledTransactionsElementEcRecovered to MockTransaction",
1178 )
1179 }
1180}
1181
1182impl From<MockTransaction> for Recovered<TransactionSigned> {
1183 fn from(tx: MockTransaction) -> Self {
1184 let hash = *tx.hash();
1185 let sender = tx.sender();
1186 let tx = Transaction::from(tx);
1187 let tx: TransactionSigned =
1188 Signed::new_unchecked(tx, Signature::test_signature(), hash).into();
1189 Self::new_unchecked(tx, sender)
1190 }
1191}
1192
1193impl From<MockTransaction> for Transaction {
1194 fn from(mock: MockTransaction) -> Self {
1195 match mock {
1196 MockTransaction::Legacy {
1197 chain_id,
1198 nonce,
1199 gas_price,
1200 gas_limit,
1201 to,
1202 value,
1203 input,
1204 ..
1205 } => Self::Legacy(TxLegacy { chain_id, nonce, gas_price, gas_limit, to, value, input }),
1206 MockTransaction::Eip2930 {
1207 chain_id,
1208 nonce,
1209 gas_price,
1210 gas_limit,
1211 to,
1212 value,
1213 access_list,
1214 input,
1215 ..
1216 } => Self::Eip2930(TxEip2930 {
1217 chain_id,
1218 nonce,
1219 gas_price,
1220 gas_limit,
1221 to,
1222 value,
1223 access_list,
1224 input,
1225 }),
1226 MockTransaction::Eip1559 {
1227 chain_id,
1228 nonce,
1229 gas_limit,
1230 max_fee_per_gas,
1231 max_priority_fee_per_gas,
1232 to,
1233 value,
1234 access_list,
1235 input,
1236 ..
1237 } => Self::Eip1559(TxEip1559 {
1238 chain_id,
1239 nonce,
1240 gas_limit,
1241 max_fee_per_gas,
1242 max_priority_fee_per_gas,
1243 to,
1244 value,
1245 access_list,
1246 input,
1247 }),
1248 MockTransaction::Eip4844 {
1249 chain_id,
1250 nonce,
1251 gas_limit,
1252 max_fee_per_gas,
1253 max_priority_fee_per_gas,
1254 to,
1255 value,
1256 access_list,
1257 sidecar,
1258 max_fee_per_blob_gas,
1259 input,
1260 ..
1261 } => Self::Eip4844(TxEip4844 {
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 blob_versioned_hashes: sidecar.versioned_hashes().collect(),
1271 max_fee_per_blob_gas,
1272 input,
1273 }),
1274 MockTransaction::Eip7702 {
1275 chain_id,
1276 nonce,
1277 gas_limit,
1278 max_fee_per_gas,
1279 max_priority_fee_per_gas,
1280 to,
1281 value,
1282 access_list,
1283 input,
1284 authorization_list,
1285 ..
1286 } => Self::Eip7702(TxEip7702 {
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 authorization_list,
1296 input,
1297 }),
1298 }
1299 }
1300}
1301
1302#[cfg(any(test, feature = "arbitrary"))]
1303impl proptest::arbitrary::Arbitrary for MockTransaction {
1304 type Parameters = ();
1305 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1306 use proptest::prelude::Strategy;
1307 use proptest_arbitrary_interop::arb;
1308
1309 arb::<(TransactionSigned, Address)>()
1310 .prop_map(|(signed_transaction, signer)| {
1311 Recovered::new_unchecked(signed_transaction, signer)
1312 .try_into()
1313 .expect("Failed to create an Arbitrary MockTransaction from a Recovered tx")
1314 })
1315 .boxed()
1316 }
1317
1318 type Strategy = proptest::strategy::BoxedStrategy<Self>;
1319}
1320
1321#[derive(Debug, Default)]
1323pub struct MockTransactionFactory {
1324 pub(crate) ids: SenderIdentifiers,
1325}
1326
1327impl MockTransactionFactory {
1330 pub fn tx_id(&mut self, tx: &MockTransaction) -> TransactionId {
1332 let sender = self.ids.sender_id_or_create(tx.sender());
1333 TransactionId::new(sender, *tx.get_nonce())
1334 }
1335
1336 pub fn validated(&mut self, transaction: MockTransaction) -> MockValidTx {
1338 self.validated_with_origin(TransactionOrigin::External, transaction)
1339 }
1340
1341 pub fn validated_arc(&mut self, transaction: MockTransaction) -> Arc<MockValidTx> {
1343 Arc::new(self.validated(transaction))
1344 }
1345
1346 pub fn validated_with_origin(
1348 &mut self,
1349 origin: TransactionOrigin,
1350 transaction: MockTransaction,
1351 ) -> MockValidTx {
1352 MockValidTx {
1353 propagate: false,
1354 transaction_id: self.tx_id(&transaction),
1355 transaction,
1356 timestamp: Instant::now(),
1357 origin,
1358 authority_ids: None,
1359 }
1360 }
1361
1362 pub fn create_legacy(&mut self) -> MockValidTx {
1364 self.validated(MockTransaction::legacy())
1365 }
1366
1367 pub fn create_eip1559(&mut self) -> MockValidTx {
1369 self.validated(MockTransaction::eip1559())
1370 }
1371
1372 pub fn create_eip4844(&mut self) -> MockValidTx {
1374 self.validated(MockTransaction::eip4844())
1375 }
1376}
1377
1378pub type MockOrdering = CoinbaseTipOrdering<MockTransaction>;
1380
1381#[derive(Debug, Clone)]
1384pub struct MockTransactionRatio {
1385 pub legacy_pct: u32,
1387 pub access_list_pct: u32,
1389 pub dynamic_fee_pct: u32,
1391 pub blob_pct: u32,
1393}
1394
1395impl MockTransactionRatio {
1396 pub fn new(legacy_pct: u32, access_list_pct: u32, dynamic_fee_pct: u32, blob_pct: u32) -> Self {
1402 let total = legacy_pct + access_list_pct + dynamic_fee_pct + blob_pct;
1403 assert_eq!(
1404 total,
1405 100,
1406 "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}",
1407 );
1408
1409 Self { legacy_pct, access_list_pct, dynamic_fee_pct, blob_pct }
1410 }
1411
1412 pub fn weighted_index(&self) -> WeightedIndex<u32> {
1420 WeightedIndex::new([
1421 self.legacy_pct,
1422 self.access_list_pct,
1423 self.dynamic_fee_pct,
1424 self.blob_pct,
1425 ])
1426 .unwrap()
1427 }
1428}
1429
1430#[derive(Debug, Clone)]
1432pub struct MockFeeRange {
1433 pub gas_price: Uniform<u128>,
1435 pub priority_fee: Uniform<u128>,
1437 pub max_fee: Uniform<u128>,
1439 pub max_fee_blob: Uniform<u128>,
1441}
1442
1443impl MockFeeRange {
1444 pub fn new(
1449 gas_price: Range<u128>,
1450 priority_fee: Range<u128>,
1451 max_fee: Range<u128>,
1452 max_fee_blob: Range<u128>,
1453 ) -> Self {
1454 assert!(
1455 max_fee.start <= priority_fee.end,
1456 "max_fee_range should be strictly below the priority fee range"
1457 );
1458 Self {
1459 gas_price: gas_price.try_into().unwrap(),
1460 priority_fee: priority_fee.try_into().unwrap(),
1461 max_fee: max_fee.try_into().unwrap(),
1462 max_fee_blob: max_fee_blob.try_into().unwrap(),
1463 }
1464 }
1465
1466 pub fn sample_gas_price(&self, rng: &mut impl rand::Rng) -> u128 {
1469 self.gas_price.sample(rng)
1470 }
1471
1472 pub fn sample_priority_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1475 self.priority_fee.sample(rng)
1476 }
1477
1478 pub fn sample_max_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1481 self.max_fee.sample(rng)
1482 }
1483
1484 pub fn sample_max_fee_blob(&self, rng: &mut impl rand::Rng) -> u128 {
1487 self.max_fee_blob.sample(rng)
1488 }
1489}
1490
1491#[derive(Debug, Clone)]
1493pub struct MockTransactionDistribution {
1494 transaction_ratio: MockTransactionRatio,
1496 gas_limit_range: Uniform<u64>,
1498 size_range: Uniform<usize>,
1500 fee_ranges: MockFeeRange,
1502}
1503
1504impl MockTransactionDistribution {
1505 pub fn new(
1507 transaction_ratio: MockTransactionRatio,
1508 fee_ranges: MockFeeRange,
1509 gas_limit_range: Range<u64>,
1510 size_range: Range<usize>,
1511 ) -> Self {
1512 Self {
1513 transaction_ratio,
1514 gas_limit_range: gas_limit_range.try_into().unwrap(),
1515 fee_ranges,
1516 size_range: size_range.try_into().unwrap(),
1517 }
1518 }
1519
1520 pub fn tx(&self, nonce: u64, rng: &mut impl rand::Rng) -> MockTransaction {
1522 let transaction_sample = self.transaction_ratio.weighted_index().sample(rng);
1523 let tx = match transaction_sample {
1524 0 => MockTransaction::legacy().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1525 1 => MockTransaction::eip2930().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1526 2 => MockTransaction::eip1559()
1527 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1528 .with_max_fee(self.fee_ranges.sample_max_fee(rng)),
1529 3 => MockTransaction::eip4844()
1530 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1531 .with_max_fee(self.fee_ranges.sample_max_fee(rng))
1532 .with_blob_fee(self.fee_ranges.sample_max_fee_blob(rng)),
1533 _ => unreachable!("unknown transaction type returned by the weighted index"),
1534 };
1535
1536 let size = self.size_range.sample(rng);
1537
1538 tx.with_nonce(nonce).with_gas_limit(self.gas_limit_range.sample(rng)).with_size(size)
1539 }
1540
1541 pub fn tx_set(
1545 &self,
1546 sender: Address,
1547 nonce_range: Range<u64>,
1548 rng: &mut impl rand::Rng,
1549 ) -> MockTransactionSet {
1550 let txs =
1551 nonce_range.map(|nonce| self.tx(nonce, rng).with_sender(sender)).collect::<Vec<_>>();
1552 MockTransactionSet::new(txs)
1553 }
1554
1555 pub fn tx_set_non_conflicting_types(
1561 &self,
1562 sender: Address,
1563 nonce_range: Range<u64>,
1564 rng: &mut impl rand::Rng,
1565 ) -> NonConflictingSetOutcome {
1566 let mut modified_distribution = self.clone();
1574 let first_tx = self.tx(nonce_range.start, rng);
1575
1576 if first_tx.is_eip4844() {
1579 modified_distribution.transaction_ratio = MockTransactionRatio {
1580 legacy_pct: 0,
1581 access_list_pct: 0,
1582 dynamic_fee_pct: 0,
1583 blob_pct: 100,
1584 };
1585
1586 NonConflictingSetOutcome::BlobsOnly(modified_distribution.tx_set(
1588 sender,
1589 nonce_range,
1590 rng,
1591 ))
1592 } else {
1593 let MockTransactionRatio { legacy_pct, access_list_pct, dynamic_fee_pct, .. } =
1594 modified_distribution.transaction_ratio;
1595
1596 let total_non_blob_weight: u32 = legacy_pct + access_list_pct + dynamic_fee_pct;
1598
1599 let new_weights: Vec<u32> = [legacy_pct, access_list_pct, dynamic_fee_pct]
1601 .into_iter()
1602 .map(|weight| weight * 100 / total_non_blob_weight)
1603 .collect();
1604
1605 let new_ratio = MockTransactionRatio {
1606 legacy_pct: new_weights[0],
1607 access_list_pct: new_weights[1],
1608 dynamic_fee_pct: new_weights[2],
1609 blob_pct: 0,
1610 };
1611
1612 modified_distribution.transaction_ratio = new_ratio;
1615
1616 NonConflictingSetOutcome::Mixed(modified_distribution.tx_set(sender, nonce_range, rng))
1618 }
1619 }
1620}
1621
1622#[derive(Debug, Clone)]
1625pub enum NonConflictingSetOutcome {
1626 BlobsOnly(MockTransactionSet),
1628 Mixed(MockTransactionSet),
1630}
1631
1632impl NonConflictingSetOutcome {
1633 pub fn into_inner(self) -> MockTransactionSet {
1635 match self {
1636 Self::BlobsOnly(set) | Self::Mixed(set) => set,
1637 }
1638 }
1639
1640 pub fn with_nonce_gaps(
1648 &mut self,
1649 gap_pct: u32,
1650 gap_range: Range<u64>,
1651 rng: &mut impl rand::Rng,
1652 ) {
1653 match self {
1654 Self::BlobsOnly(_) => {}
1655 Self::Mixed(set) => set.with_nonce_gaps(gap_pct, gap_range, rng),
1656 }
1657 }
1658}
1659
1660#[derive(Debug, Clone)]
1662pub struct MockTransactionSet {
1663 pub(crate) transactions: Vec<MockTransaction>,
1664}
1665
1666impl MockTransactionSet {
1667 const fn new(transactions: Vec<MockTransaction>) -> Self {
1669 Self { transactions }
1670 }
1671
1672 pub fn dependent(sender: Address, from_nonce: u64, tx_count: usize, tx_type: TxType) -> Self {
1679 let mut txs = Vec::with_capacity(tx_count);
1680 let mut curr_tx =
1681 MockTransaction::new_from_type(tx_type).with_nonce(from_nonce).with_sender(sender);
1682 for _ in 0..tx_count {
1683 txs.push(curr_tx.clone());
1684 curr_tx = curr_tx.next();
1685 }
1686
1687 Self::new(txs)
1688 }
1689
1690 pub fn sequential_transactions_by_sender(
1695 sender: Address,
1696 tx_count: usize,
1697 tx_type: TxType,
1698 ) -> Self {
1699 Self::dependent(sender, 0, tx_count, tx_type)
1700 }
1701
1702 pub fn with_nonce_gaps(
1716 &mut self,
1717 gap_pct: u32,
1718 gap_range: Range<u64>,
1719 rng: &mut impl rand::Rng,
1720 ) {
1721 assert!(gap_pct <= 100, "gap_pct must be between 0 and 100");
1722 assert!(gap_range.start >= 1, "gap_range must have a lower bound of at least one");
1723
1724 let mut prev_nonce = 0;
1725 for tx in &mut self.transactions {
1726 if rng.random_bool(gap_pct as f64 / 100.0) {
1727 prev_nonce += gap_range.start;
1728 } else {
1729 prev_nonce += 1;
1730 }
1731 tx.set_nonce(prev_nonce);
1732 }
1733 }
1734
1735 pub fn extend<T: IntoIterator<Item = MockTransaction>>(&mut self, txs: T) {
1737 self.transactions.extend(txs);
1738 }
1739
1740 pub fn into_vec(self) -> Vec<MockTransaction> {
1742 self.transactions
1743 }
1744
1745 pub fn iter(&self) -> impl Iterator<Item = &MockTransaction> {
1747 self.transactions.iter()
1748 }
1749
1750 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MockTransaction> {
1752 self.transactions.iter_mut()
1753 }
1754}
1755
1756impl IntoIterator for MockTransactionSet {
1757 type Item = MockTransaction;
1758 type IntoIter = IntoIter<MockTransaction>;
1759
1760 fn into_iter(self) -> Self::IntoIter {
1761 self.transactions.into_iter()
1762 }
1763}
1764
1765#[test]
1766fn test_mock_priority() {
1767 use crate::TransactionOrdering;
1768
1769 let o = MockOrdering::default();
1770 let lo = MockTransaction::eip1559().with_gas_limit(100_000);
1771 let hi = lo.next().inc_price();
1772 assert!(o.priority(&hi, 0) > o.priority(&lo, 0));
1773}
1774
1775#[cfg(test)]
1776mod tests {
1777 use super::*;
1778 use alloy_consensus::Transaction;
1779 use alloy_primitives::U256;
1780
1781 #[test]
1782 fn test_mock_transaction_factory() {
1783 let mut factory = MockTransactionFactory::default();
1784
1785 let legacy = factory.create_legacy();
1787 assert_eq!(legacy.transaction.tx_type(), TxType::Legacy);
1788
1789 let eip1559 = factory.create_eip1559();
1791 assert_eq!(eip1559.transaction.tx_type(), TxType::Eip1559);
1792
1793 let eip4844 = factory.create_eip4844();
1795 assert_eq!(eip4844.transaction.tx_type(), TxType::Eip4844);
1796 }
1797
1798 #[test]
1799 fn test_mock_transaction_set() {
1800 let sender = Address::random();
1801 let nonce_start = 0u64;
1802 let count = 3;
1803
1804 let legacy_set = MockTransactionSet::dependent(sender, nonce_start, count, TxType::Legacy);
1806 assert_eq!(legacy_set.transactions.len(), count);
1807 for (idx, tx) in legacy_set.transactions.iter().enumerate() {
1808 assert_eq!(tx.tx_type(), TxType::Legacy);
1809 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1810 assert_eq!(tx.sender(), sender);
1811 }
1812
1813 let eip1559_set =
1815 MockTransactionSet::dependent(sender, nonce_start, count, TxType::Eip1559);
1816 assert_eq!(eip1559_set.transactions.len(), count);
1817 for (idx, tx) in eip1559_set.transactions.iter().enumerate() {
1818 assert_eq!(tx.tx_type(), TxType::Eip1559);
1819 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1820 assert_eq!(tx.sender(), sender);
1821 }
1822 }
1823
1824 #[test]
1825 fn test_mock_transaction_modifications() {
1826 let tx = MockTransaction::eip1559();
1827
1828 let original_price = tx.get_gas_price();
1830 let tx_inc = tx.inc_price();
1831 assert!(tx_inc.get_gas_price() > original_price);
1832
1833 let original_limit = tx.gas_limit();
1835 let tx_inc = tx.inc_limit();
1836 assert!(tx_inc.gas_limit() > original_limit);
1837
1838 let original_nonce = tx.nonce();
1840 let tx_inc = tx.inc_nonce();
1841 assert_eq!(tx_inc.nonce(), original_nonce + 1);
1842 }
1843
1844 #[test]
1845 fn test_mock_transaction_cost() {
1846 let tx = MockTransaction::eip1559()
1847 .with_gas_limit(7_000)
1848 .with_max_fee(100)
1849 .with_value(U256::ZERO);
1850
1851 let expected_cost = U256::from(7_000u64) * U256::from(100u128) + U256::ZERO;
1853 assert_eq!(*tx.cost(), expected_cost);
1854 }
1855}