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 transaction::PooledTransaction,
16 EthereumTxEnvelope, Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702,
17 TxEnvelope, TxLegacy, TxType, Typed2718,
18};
19use alloy_eips::{
20 eip1559::MIN_PROTOCOL_BASE_FEE,
21 eip2930::AccessList,
22 eip4844::{BlobTransactionSidecar, BlobTransactionValidationError, DATA_GAS_PER_BLOB},
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::{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: BlobTransactionSidecar,
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: 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: BlobTransactionSidecar) -> 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 const fn set_gas_price(&mut self, val: u128) -> &mut Self {
487 match self {
488 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => {
489 *gas_price = val;
490 }
491 Self::Eip1559 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
492 Self::Eip4844 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
493 Self::Eip7702 { max_fee_per_gas, max_priority_fee_per_gas, .. } => {
494 *max_fee_per_gas = val;
495 *max_priority_fee_per_gas = val;
496 }
497 }
498 self
499 }
500
501 pub const fn with_gas_price(mut self, val: u128) -> Self {
503 match self {
504 Self::Legacy { ref mut gas_price, .. } | Self::Eip2930 { ref mut gas_price, .. } => {
505 *gas_price = val;
506 }
507 Self::Eip1559 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
508 Self::Eip4844 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
509 Self::Eip7702 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } => {
510 *max_fee_per_gas = val;
511 *max_priority_fee_per_gas = val;
512 }
513 }
514 self
515 }
516
517 pub const fn get_gas_price(&self) -> u128 {
519 match self {
520 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
521 Self::Eip1559 { max_fee_per_gas, .. } |
522 Self::Eip4844 { max_fee_per_gas, .. } |
523 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
524 }
525 }
526
527 pub fn prev(&self) -> Self {
529 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() - 1)
530 }
531
532 pub fn next(&self) -> Self {
534 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + 1)
535 }
536
537 pub fn skip(&self, skip: u64) -> Self {
539 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + skip + 1)
540 }
541
542 pub fn inc_nonce(self) -> Self {
544 let nonce = self.get_nonce() + 1;
545 self.with_nonce(nonce)
546 }
547
548 pub fn rng_hash(self) -> Self {
550 self.with_hash(B256::random())
551 }
552
553 pub fn inc_price(&self) -> Self {
555 self.inc_price_by(1)
556 }
557
558 pub fn inc_price_by(&self, value: u128) -> Self {
560 self.clone().with_gas_price(self.get_gas_price().checked_add(value).unwrap())
561 }
562
563 pub fn decr_price(&self) -> Self {
565 self.decr_price_by(1)
566 }
567
568 pub fn decr_price_by(&self, value: u128) -> Self {
570 self.clone().with_gas_price(self.get_gas_price().checked_sub(value).unwrap())
571 }
572
573 pub fn inc_value(&self) -> Self {
575 self.clone().with_value(self.get_value().checked_add(U256::from(1)).unwrap())
576 }
577
578 pub fn inc_limit(&self) -> Self {
580 self.clone().with_gas_limit(self.get_gas_limit() + 1)
581 }
582
583 pub fn inc_blob_fee(&self) -> Self {
587 self.inc_blob_fee_by(1)
588 }
589
590 pub fn inc_blob_fee_by(&self, value: u128) -> Self {
594 let mut this = self.clone();
595 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
596 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_add(value).unwrap();
597 }
598 this
599 }
600
601 pub fn decr_blob_fee(&self) -> Self {
605 self.decr_price_by(1)
606 }
607
608 pub fn decr_blob_fee_by(&self, value: u128) -> Self {
612 let mut this = self.clone();
613 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
614 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_sub(value).unwrap();
615 }
616 this
617 }
618
619 pub const fn tx_type(&self) -> u8 {
621 match self {
622 Self::Legacy { .. } => LEGACY_TX_TYPE_ID,
623 Self::Eip1559 { .. } => EIP1559_TX_TYPE_ID,
624 Self::Eip4844 { .. } => EIP4844_TX_TYPE_ID,
625 Self::Eip2930 { .. } => EIP2930_TX_TYPE_ID,
626 Self::Eip7702 { .. } => EIP7702_TX_TYPE_ID,
627 }
628 }
629
630 pub const fn is_legacy(&self) -> bool {
632 matches!(self, Self::Legacy { .. })
633 }
634
635 pub const fn is_eip1559(&self) -> bool {
637 matches!(self, Self::Eip1559 { .. })
638 }
639
640 pub const fn is_eip4844(&self) -> bool {
642 matches!(self, Self::Eip4844 { .. })
643 }
644
645 pub const fn is_eip2930(&self) -> bool {
647 matches!(self, Self::Eip2930 { .. })
648 }
649
650 pub const fn is_eip7702(&self) -> bool {
652 matches!(self, Self::Eip7702 { .. })
653 }
654
655 fn update_cost(&mut self) {
656 match self {
657 Self::Legacy { cost, gas_limit, gas_price, value, .. } |
658 Self::Eip2930 { cost, gas_limit, gas_price, value, .. } => {
659 *cost = U256::from(*gas_limit) * U256::from(*gas_price) + *value
660 }
661 Self::Eip1559 { cost, gas_limit, max_fee_per_gas, value, .. } |
662 Self::Eip4844 { cost, gas_limit, max_fee_per_gas, value, .. } |
663 Self::Eip7702 { cost, gas_limit, max_fee_per_gas, value, .. } => {
664 *cost = U256::from(*gas_limit) * U256::from(*max_fee_per_gas) + *value
665 }
666 };
667 }
668}
669
670impl PoolTransaction for MockTransaction {
671 type TryFromConsensusError = ValueError<EthereumTxEnvelope<TxEip4844>>;
672
673 type Consensus = TransactionSigned;
674
675 type Pooled = PooledTransaction;
676
677 fn into_consensus(self) -> Recovered<Self::Consensus> {
678 self.into()
679 }
680
681 fn from_pooled(pooled: Recovered<Self::Pooled>) -> Self {
682 pooled.into()
683 }
684
685 fn hash(&self) -> &TxHash {
686 self.get_hash()
687 }
688
689 fn sender(&self) -> Address {
690 *self.get_sender()
691 }
692
693 fn sender_ref(&self) -> &Address {
694 self.get_sender()
695 }
696
697 fn cost(&self) -> &U256 {
702 match self {
703 Self::Legacy { cost, .. } |
704 Self::Eip2930 { cost, .. } |
705 Self::Eip1559 { cost, .. } |
706 Self::Eip4844 { cost, .. } |
707 Self::Eip7702 { cost, .. } => cost,
708 }
709 }
710
711 fn encoded_length(&self) -> usize {
713 self.size()
714 }
715}
716
717impl InMemorySize for MockTransaction {
718 fn size(&self) -> usize {
719 *self.get_size()
720 }
721}
722
723impl Typed2718 for MockTransaction {
724 fn ty(&self) -> u8 {
725 match self {
726 Self::Legacy { .. } => TxType::Legacy.into(),
727 Self::Eip1559 { .. } => TxType::Eip1559.into(),
728 Self::Eip4844 { .. } => TxType::Eip4844.into(),
729 Self::Eip2930 { .. } => TxType::Eip2930.into(),
730 Self::Eip7702 { .. } => TxType::Eip7702.into(),
731 }
732 }
733}
734
735impl alloy_consensus::Transaction for MockTransaction {
736 fn chain_id(&self) -> Option<u64> {
737 match self {
738 Self::Legacy { chain_id, .. } => *chain_id,
739 Self::Eip1559 { chain_id, .. } |
740 Self::Eip4844 { chain_id, .. } |
741 Self::Eip2930 { chain_id, .. } |
742 Self::Eip7702 { chain_id, .. } => Some(*chain_id),
743 }
744 }
745
746 fn nonce(&self) -> u64 {
747 *self.get_nonce()
748 }
749
750 fn gas_limit(&self) -> u64 {
751 *self.get_gas_limit()
752 }
753
754 fn gas_price(&self) -> Option<u128> {
755 match self {
756 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => Some(*gas_price),
757 _ => None,
758 }
759 }
760
761 fn max_fee_per_gas(&self) -> u128 {
762 match self {
763 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
764 Self::Eip1559 { max_fee_per_gas, .. } |
765 Self::Eip4844 { max_fee_per_gas, .. } |
766 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
767 }
768 }
769
770 fn max_priority_fee_per_gas(&self) -> Option<u128> {
771 match self {
772 Self::Legacy { .. } | Self::Eip2930 { .. } => None,
773 Self::Eip1559 { max_priority_fee_per_gas, .. } |
774 Self::Eip4844 { max_priority_fee_per_gas, .. } |
775 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
776 }
777 }
778
779 fn max_fee_per_blob_gas(&self) -> Option<u128> {
780 match self {
781 Self::Eip4844 { max_fee_per_blob_gas, .. } => Some(*max_fee_per_blob_gas),
782 _ => None,
783 }
784 }
785
786 fn priority_fee_or_price(&self) -> u128 {
787 match self {
788 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
789 Self::Eip1559 { max_priority_fee_per_gas, .. } |
790 Self::Eip4844 { max_priority_fee_per_gas, .. } |
791 Self::Eip7702 { max_priority_fee_per_gas, .. } => *max_priority_fee_per_gas,
792 }
793 }
794
795 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
796 base_fee.map_or(self.max_fee_per_gas(), |base_fee| {
797 let tip = self.max_fee_per_gas().saturating_sub(base_fee as u128);
800 if let Some(max_tip) = self.max_priority_fee_per_gas() {
801 if tip > max_tip {
802 max_tip + base_fee as u128
803 } else {
804 self.max_fee_per_gas()
806 }
807 } else {
808 self.max_fee_per_gas()
809 }
810 })
811 }
812
813 fn is_dynamic_fee(&self) -> bool {
814 !matches!(self, Self::Legacy { .. } | Self::Eip2930 { .. })
815 }
816
817 fn kind(&self) -> TxKind {
818 match self {
819 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => *to,
820 Self::Eip4844 { to, .. } | Self::Eip7702 { to, .. } => TxKind::Call(*to),
821 }
822 }
823
824 fn is_create(&self) -> bool {
825 match self {
826 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => {
827 to.is_create()
828 }
829 Self::Eip4844 { .. } | Self::Eip7702 { .. } => false,
830 }
831 }
832
833 fn value(&self) -> U256 {
834 match self {
835 Self::Legacy { value, .. } |
836 Self::Eip1559 { value, .. } |
837 Self::Eip2930 { value, .. } |
838 Self::Eip4844 { value, .. } |
839 Self::Eip7702 { value, .. } => *value,
840 }
841 }
842
843 fn input(&self) -> &Bytes {
844 self.get_input()
845 }
846
847 fn access_list(&self) -> Option<&AccessList> {
848 match self {
849 Self::Legacy { .. } => None,
850 Self::Eip1559 { access_list: accesslist, .. } |
851 Self::Eip4844 { access_list: accesslist, .. } |
852 Self::Eip2930 { access_list: accesslist, .. } |
853 Self::Eip7702 { access_list: accesslist, .. } => Some(accesslist),
854 }
855 }
856
857 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
858 match self {
859 Self::Eip4844 { blob_versioned_hashes, .. } => Some(blob_versioned_hashes),
860 _ => None,
861 }
862 }
863
864 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
865 match self {
866 Self::Eip7702 { authorization_list, .. } => Some(authorization_list),
867 _ => None,
868 }
869 }
870}
871
872impl EthPoolTransaction for MockTransaction {
873 fn take_blob(&mut self) -> EthBlobTransactionSidecar {
874 match self {
875 Self::Eip4844 { sidecar, .. } => EthBlobTransactionSidecar::Present(sidecar.clone()),
876 _ => EthBlobTransactionSidecar::None,
877 }
878 }
879
880 fn try_into_pooled_eip4844(
881 self,
882 sidecar: Arc<BlobTransactionSidecar>,
883 ) -> Option<Recovered<Self::Pooled>> {
884 let (tx, signer) = self.into_consensus().into_parts();
885 tx.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar))
886 .map(|tx| tx.with_signer(signer))
887 .ok()
888 }
889
890 fn try_from_eip4844(
891 tx: Recovered<Self::Consensus>,
892 sidecar: BlobTransactionSidecar,
893 ) -> Option<Self> {
894 let (tx, signer) = tx.into_parts();
895 tx.try_into_pooled_eip4844(sidecar)
896 .map(|tx| tx.with_signer(signer))
897 .ok()
898 .map(Self::from_pooled)
899 }
900
901 fn validate_blob(
902 &self,
903 _blob: &BlobTransactionSidecar,
904 _settings: &KzgSettings,
905 ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> {
906 match &self {
907 Self::Eip4844 { .. } => Ok(()),
908 _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
909 }
910 }
911}
912
913impl TryFrom<Recovered<TransactionSigned>> for MockTransaction {
914 type Error = TryFromRecoveredTransactionError;
915
916 fn try_from(tx: Recovered<TransactionSigned>) -> Result<Self, Self::Error> {
917 let sender = tx.signer();
918 let transaction = tx.into_inner();
919 let hash = *transaction.tx_hash();
920 let size = transaction.size();
921
922 match transaction.into_typed_transaction() {
923 Transaction::Legacy(TxLegacy {
924 chain_id,
925 nonce,
926 gas_price,
927 gas_limit,
928 to,
929 value,
930 input,
931 }) => Ok(Self::Legacy {
932 chain_id,
933 hash,
934 sender,
935 nonce,
936 gas_price,
937 gas_limit,
938 to,
939 value,
940 input,
941 size,
942 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
943 }),
944 Transaction::Eip2930(TxEip2930 {
945 chain_id,
946 nonce,
947 gas_price,
948 gas_limit,
949 to,
950 value,
951 input,
952 access_list,
953 }) => Ok(Self::Eip2930 {
954 chain_id,
955 hash,
956 sender,
957 nonce,
958 gas_price,
959 gas_limit,
960 to,
961 value,
962 input,
963 access_list,
964 size,
965 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
966 }),
967 Transaction::Eip1559(TxEip1559 {
968 chain_id,
969 nonce,
970 gas_limit,
971 max_fee_per_gas,
972 max_priority_fee_per_gas,
973 to,
974 value,
975 input,
976 access_list,
977 }) => Ok(Self::Eip1559 {
978 chain_id,
979 hash,
980 sender,
981 nonce,
982 max_fee_per_gas,
983 max_priority_fee_per_gas,
984 gas_limit,
985 to,
986 value,
987 input,
988 access_list,
989 size,
990 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
991 }),
992 Transaction::Eip4844(TxEip4844 {
993 chain_id,
994 nonce,
995 gas_limit,
996 max_fee_per_gas,
997 max_priority_fee_per_gas,
998 to,
999 value,
1000 input,
1001 access_list,
1002 blob_versioned_hashes: _,
1003 max_fee_per_blob_gas,
1004 }) => Ok(Self::Eip4844 {
1005 chain_id,
1006 hash,
1007 sender,
1008 nonce,
1009 max_fee_per_gas,
1010 max_priority_fee_per_gas,
1011 max_fee_per_blob_gas,
1012 gas_limit,
1013 to,
1014 value,
1015 input,
1016 access_list,
1017 sidecar: BlobTransactionSidecar::default(),
1018 blob_versioned_hashes: Default::default(),
1019 size,
1020 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1021 }),
1022 Transaction::Eip7702(TxEip7702 {
1023 chain_id,
1024 nonce,
1025 gas_limit,
1026 max_fee_per_gas,
1027 max_priority_fee_per_gas,
1028 to,
1029 value,
1030 access_list,
1031 authorization_list,
1032 input,
1033 }) => Ok(Self::Eip7702 {
1034 chain_id,
1035 hash,
1036 sender,
1037 nonce,
1038 max_fee_per_gas,
1039 max_priority_fee_per_gas,
1040 gas_limit,
1041 to,
1042 value,
1043 input,
1044 access_list,
1045 authorization_list,
1046 size,
1047 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1048 }),
1049 }
1050 }
1051}
1052
1053impl TryFrom<Recovered<TxEnvelope>> for MockTransaction {
1054 type Error = TryFromRecoveredTransactionError;
1055
1056 fn try_from(tx: Recovered<TxEnvelope>) -> Result<Self, Self::Error> {
1057 let sender = tx.signer();
1058 let transaction = tx.into_inner();
1059 let hash = *transaction.tx_hash();
1060 let size = transaction.size();
1061
1062 match transaction {
1063 EthereumTxEnvelope::Legacy(signed_tx) => {
1064 let tx = signed_tx.strip_signature();
1065 Ok(Self::Legacy {
1066 chain_id: tx.chain_id,
1067 hash,
1068 sender,
1069 nonce: tx.nonce,
1070 gas_price: tx.gas_price,
1071 gas_limit: tx.gas_limit,
1072 to: tx.to,
1073 value: tx.value,
1074 input: tx.input,
1075 size,
1076 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1077 })
1078 }
1079 EthereumTxEnvelope::Eip2930(signed_tx) => {
1080 let tx = signed_tx.strip_signature();
1081 Ok(Self::Eip2930 {
1082 chain_id: tx.chain_id,
1083 hash,
1084 sender,
1085 nonce: tx.nonce,
1086 gas_price: tx.gas_price,
1087 gas_limit: tx.gas_limit,
1088 to: tx.to,
1089 value: tx.value,
1090 input: tx.input,
1091 access_list: tx.access_list,
1092 size,
1093 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1094 })
1095 }
1096 EthereumTxEnvelope::Eip1559(signed_tx) => {
1097 let tx = signed_tx.strip_signature();
1098 Ok(Self::Eip1559 {
1099 chain_id: tx.chain_id,
1100 hash,
1101 sender,
1102 nonce: tx.nonce,
1103 max_fee_per_gas: tx.max_fee_per_gas,
1104 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1105 gas_limit: tx.gas_limit,
1106 to: tx.to,
1107 value: tx.value,
1108 input: tx.input,
1109 access_list: tx.access_list,
1110 size,
1111 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1112 })
1113 }
1114 EthereumTxEnvelope::Eip4844(signed_tx) => match signed_tx.tx() {
1115 TxEip4844Variant::TxEip4844(tx) => Ok(Self::Eip4844 {
1116 chain_id: tx.chain_id,
1117 hash,
1118 sender,
1119 nonce: tx.nonce,
1120 max_fee_per_gas: tx.max_fee_per_gas,
1121 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1122 max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
1123 gas_limit: tx.gas_limit,
1124 to: tx.to,
1125 value: tx.value,
1126 input: tx.input.clone(),
1127 access_list: tx.access_list.clone(),
1128 sidecar: BlobTransactionSidecar::default(),
1129 blob_versioned_hashes: tx.blob_versioned_hashes.clone(),
1130 size,
1131 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1132 }),
1133 tx => Err(TryFromRecoveredTransactionError::UnsupportedTransactionType(tx.ty())),
1134 },
1135 EthereumTxEnvelope::Eip7702(signed_tx) => {
1136 let tx = signed_tx.strip_signature();
1137 Ok(Self::Eip7702 {
1138 chain_id: tx.chain_id,
1139 hash,
1140 sender,
1141 nonce: tx.nonce,
1142 max_fee_per_gas: tx.max_fee_per_gas,
1143 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1144 gas_limit: tx.gas_limit,
1145 to: tx.to,
1146 value: tx.value,
1147 access_list: tx.access_list,
1148 authorization_list: tx.authorization_list,
1149 input: tx.input,
1150 size,
1151 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1152 })
1153 }
1154 }
1155 }
1156}
1157
1158impl From<Recovered<PooledTransaction>> for MockTransaction {
1159 fn from(tx: Recovered<PooledTransaction>) -> Self {
1160 let (tx, signer) = tx.into_parts();
1161 Recovered::<TransactionSigned>::new_unchecked(tx.into(), signer).try_into().expect(
1162 "Failed to convert from PooledTransactionsElementEcRecovered to MockTransaction",
1163 )
1164 }
1165}
1166
1167impl From<MockTransaction> for Recovered<TransactionSigned> {
1168 fn from(tx: MockTransaction) -> Self {
1169 let hash = *tx.hash();
1170 let sender = tx.sender();
1171 let tx = Transaction::from(tx);
1172 let tx: TransactionSigned =
1173 Signed::new_unchecked(tx, Signature::test_signature(), hash).into();
1174 Self::new_unchecked(tx, sender)
1175 }
1176}
1177
1178impl From<MockTransaction> for Transaction {
1179 fn from(mock: MockTransaction) -> Self {
1180 match mock {
1181 MockTransaction::Legacy {
1182 chain_id,
1183 nonce,
1184 gas_price,
1185 gas_limit,
1186 to,
1187 value,
1188 input,
1189 ..
1190 } => Self::Legacy(TxLegacy { chain_id, nonce, gas_price, gas_limit, to, value, input }),
1191 MockTransaction::Eip2930 {
1192 chain_id,
1193 nonce,
1194 gas_price,
1195 gas_limit,
1196 to,
1197 value,
1198 access_list,
1199 input,
1200 ..
1201 } => Self::Eip2930(TxEip2930 {
1202 chain_id,
1203 nonce,
1204 gas_price,
1205 gas_limit,
1206 to,
1207 value,
1208 access_list,
1209 input,
1210 }),
1211 MockTransaction::Eip1559 {
1212 chain_id,
1213 nonce,
1214 gas_limit,
1215 max_fee_per_gas,
1216 max_priority_fee_per_gas,
1217 to,
1218 value,
1219 access_list,
1220 input,
1221 ..
1222 } => Self::Eip1559(TxEip1559 {
1223 chain_id,
1224 nonce,
1225 gas_limit,
1226 max_fee_per_gas,
1227 max_priority_fee_per_gas,
1228 to,
1229 value,
1230 access_list,
1231 input,
1232 }),
1233 MockTransaction::Eip4844 {
1234 chain_id,
1235 nonce,
1236 gas_limit,
1237 max_fee_per_gas,
1238 max_priority_fee_per_gas,
1239 to,
1240 value,
1241 access_list,
1242 sidecar,
1243 max_fee_per_blob_gas,
1244 input,
1245 ..
1246 } => Self::Eip4844(TxEip4844 {
1247 chain_id,
1248 nonce,
1249 gas_limit,
1250 max_fee_per_gas,
1251 max_priority_fee_per_gas,
1252 to,
1253 value,
1254 access_list,
1255 blob_versioned_hashes: sidecar.versioned_hashes().collect(),
1256 max_fee_per_blob_gas,
1257 input,
1258 }),
1259 MockTransaction::Eip7702 {
1260 chain_id,
1261 nonce,
1262 gas_limit,
1263 max_fee_per_gas,
1264 max_priority_fee_per_gas,
1265 to,
1266 value,
1267 access_list,
1268 input,
1269 authorization_list,
1270 ..
1271 } => Self::Eip7702(TxEip7702 {
1272 chain_id,
1273 nonce,
1274 gas_limit,
1275 max_fee_per_gas,
1276 max_priority_fee_per_gas,
1277 to,
1278 value,
1279 access_list,
1280 authorization_list,
1281 input,
1282 }),
1283 }
1284 }
1285}
1286
1287#[cfg(any(test, feature = "arbitrary"))]
1288impl proptest::arbitrary::Arbitrary for MockTransaction {
1289 type Parameters = ();
1290 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1291 use proptest::prelude::Strategy;
1292 use proptest_arbitrary_interop::arb;
1293
1294 arb::<(TransactionSigned, Address)>()
1295 .prop_map(|(signed_transaction, signer)| {
1296 Recovered::new_unchecked(signed_transaction, signer)
1297 .try_into()
1298 .expect("Failed to create an Arbitrary MockTransaction from a Recovered tx")
1299 })
1300 .boxed()
1301 }
1302
1303 type Strategy = proptest::strategy::BoxedStrategy<Self>;
1304}
1305
1306#[derive(Debug, Default)]
1308pub struct MockTransactionFactory {
1309 pub(crate) ids: SenderIdentifiers,
1310}
1311
1312impl MockTransactionFactory {
1315 pub fn tx_id(&mut self, tx: &MockTransaction) -> TransactionId {
1317 let sender = self.ids.sender_id_or_create(tx.sender());
1318 TransactionId::new(sender, *tx.get_nonce())
1319 }
1320
1321 pub fn validated(&mut self, transaction: MockTransaction) -> MockValidTx {
1323 self.validated_with_origin(TransactionOrigin::External, transaction)
1324 }
1325
1326 pub fn validated_arc(&mut self, transaction: MockTransaction) -> Arc<MockValidTx> {
1328 Arc::new(self.validated(transaction))
1329 }
1330
1331 pub fn validated_with_origin(
1333 &mut self,
1334 origin: TransactionOrigin,
1335 transaction: MockTransaction,
1336 ) -> MockValidTx {
1337 MockValidTx {
1338 propagate: false,
1339 transaction_id: self.tx_id(&transaction),
1340 transaction,
1341 timestamp: Instant::now(),
1342 origin,
1343 }
1344 }
1345
1346 pub fn create_legacy(&mut self) -> MockValidTx {
1348 self.validated(MockTransaction::legacy())
1349 }
1350
1351 pub fn create_eip1559(&mut self) -> MockValidTx {
1353 self.validated(MockTransaction::eip1559())
1354 }
1355
1356 pub fn create_eip4844(&mut self) -> MockValidTx {
1358 self.validated(MockTransaction::eip4844())
1359 }
1360}
1361
1362pub type MockOrdering = CoinbaseTipOrdering<MockTransaction>;
1364
1365#[derive(Debug, Clone)]
1368pub struct MockTransactionRatio {
1369 pub legacy_pct: u32,
1371 pub access_list_pct: u32,
1373 pub dynamic_fee_pct: u32,
1375 pub blob_pct: u32,
1377}
1378
1379impl MockTransactionRatio {
1380 pub fn new(legacy_pct: u32, access_list_pct: u32, dynamic_fee_pct: u32, blob_pct: u32) -> Self {
1386 let total = legacy_pct + access_list_pct + dynamic_fee_pct + blob_pct;
1387 assert_eq!(
1388 total,
1389 100,
1390 "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}",
1391 );
1392
1393 Self { legacy_pct, access_list_pct, dynamic_fee_pct, blob_pct }
1394 }
1395
1396 pub fn weighted_index(&self) -> WeightedIndex<u32> {
1404 WeightedIndex::new([
1405 self.legacy_pct,
1406 self.access_list_pct,
1407 self.dynamic_fee_pct,
1408 self.blob_pct,
1409 ])
1410 .unwrap()
1411 }
1412}
1413
1414#[derive(Debug, Clone)]
1416pub struct MockFeeRange {
1417 pub gas_price: Uniform<u128>,
1419 pub priority_fee: Uniform<u128>,
1421 pub max_fee: Uniform<u128>,
1423 pub max_fee_blob: Uniform<u128>,
1425}
1426
1427impl MockFeeRange {
1428 pub fn new(
1433 gas_price: Range<u128>,
1434 priority_fee: Range<u128>,
1435 max_fee: Range<u128>,
1436 max_fee_blob: Range<u128>,
1437 ) -> Self {
1438 assert!(
1439 max_fee.start <= priority_fee.end,
1440 "max_fee_range should be strictly below the priority fee range"
1441 );
1442 Self {
1443 gas_price: gas_price.try_into().unwrap(),
1444 priority_fee: priority_fee.try_into().unwrap(),
1445 max_fee: max_fee.try_into().unwrap(),
1446 max_fee_blob: max_fee_blob.try_into().unwrap(),
1447 }
1448 }
1449
1450 pub fn sample_gas_price(&self, rng: &mut impl rand::Rng) -> u128 {
1453 self.gas_price.sample(rng)
1454 }
1455
1456 pub fn sample_priority_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1459 self.priority_fee.sample(rng)
1460 }
1461
1462 pub fn sample_max_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1465 self.max_fee.sample(rng)
1466 }
1467
1468 pub fn sample_max_fee_blob(&self, rng: &mut impl rand::Rng) -> u128 {
1471 self.max_fee_blob.sample(rng)
1472 }
1473}
1474
1475#[derive(Debug, Clone)]
1477pub struct MockTransactionDistribution {
1478 transaction_ratio: MockTransactionRatio,
1480 gas_limit_range: Uniform<u64>,
1482 size_range: Uniform<usize>,
1484 fee_ranges: MockFeeRange,
1486}
1487
1488impl MockTransactionDistribution {
1489 pub fn new(
1491 transaction_ratio: MockTransactionRatio,
1492 fee_ranges: MockFeeRange,
1493 gas_limit_range: Range<u64>,
1494 size_range: Range<usize>,
1495 ) -> Self {
1496 Self {
1497 transaction_ratio,
1498 gas_limit_range: gas_limit_range.try_into().unwrap(),
1499 fee_ranges,
1500 size_range: size_range.try_into().unwrap(),
1501 }
1502 }
1503
1504 pub fn tx(&self, nonce: u64, rng: &mut impl rand::Rng) -> MockTransaction {
1506 let transaction_sample = self.transaction_ratio.weighted_index().sample(rng);
1507 let tx = match transaction_sample {
1508 0 => MockTransaction::legacy().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1509 1 => MockTransaction::eip2930().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1510 2 => MockTransaction::eip1559()
1511 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1512 .with_max_fee(self.fee_ranges.sample_max_fee(rng)),
1513 3 => MockTransaction::eip4844()
1514 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1515 .with_max_fee(self.fee_ranges.sample_max_fee(rng))
1516 .with_blob_fee(self.fee_ranges.sample_max_fee_blob(rng)),
1517 _ => unreachable!("unknown transaction type returned by the weighted index"),
1518 };
1519
1520 let size = self.size_range.sample(rng);
1521
1522 tx.with_nonce(nonce).with_gas_limit(self.gas_limit_range.sample(rng)).with_size(size)
1523 }
1524
1525 pub fn tx_set(
1529 &self,
1530 sender: Address,
1531 nonce_range: Range<u64>,
1532 rng: &mut impl rand::Rng,
1533 ) -> MockTransactionSet {
1534 let txs =
1535 nonce_range.map(|nonce| self.tx(nonce, rng).with_sender(sender)).collect::<Vec<_>>();
1536 MockTransactionSet::new(txs)
1537 }
1538
1539 pub fn tx_set_non_conflicting_types(
1545 &self,
1546 sender: Address,
1547 nonce_range: Range<u64>,
1548 rng: &mut impl rand::Rng,
1549 ) -> NonConflictingSetOutcome {
1550 let mut modified_distribution = self.clone();
1558 let first_tx = self.tx(nonce_range.start, rng);
1559
1560 if first_tx.is_eip4844() {
1563 modified_distribution.transaction_ratio = MockTransactionRatio {
1564 legacy_pct: 0,
1565 access_list_pct: 0,
1566 dynamic_fee_pct: 0,
1567 blob_pct: 100,
1568 };
1569
1570 NonConflictingSetOutcome::BlobsOnly(modified_distribution.tx_set(
1572 sender,
1573 nonce_range,
1574 rng,
1575 ))
1576 } else {
1577 let MockTransactionRatio { legacy_pct, access_list_pct, dynamic_fee_pct, .. } =
1578 modified_distribution.transaction_ratio;
1579
1580 let total_non_blob_weight: u32 = legacy_pct + access_list_pct + dynamic_fee_pct;
1582
1583 let new_weights: Vec<u32> = [legacy_pct, access_list_pct, dynamic_fee_pct]
1585 .into_iter()
1586 .map(|weight| weight * 100 / total_non_blob_weight)
1587 .collect();
1588
1589 let new_ratio = MockTransactionRatio {
1590 legacy_pct: new_weights[0],
1591 access_list_pct: new_weights[1],
1592 dynamic_fee_pct: new_weights[2],
1593 blob_pct: 0,
1594 };
1595
1596 modified_distribution.transaction_ratio = new_ratio;
1599
1600 NonConflictingSetOutcome::Mixed(modified_distribution.tx_set(sender, nonce_range, rng))
1602 }
1603 }
1604}
1605
1606#[derive(Debug, Clone)]
1609pub enum NonConflictingSetOutcome {
1610 BlobsOnly(MockTransactionSet),
1612 Mixed(MockTransactionSet),
1614}
1615
1616impl NonConflictingSetOutcome {
1617 pub fn into_inner(self) -> MockTransactionSet {
1619 match self {
1620 Self::BlobsOnly(set) | Self::Mixed(set) => set,
1621 }
1622 }
1623
1624 pub fn with_nonce_gaps(
1632 &mut self,
1633 gap_pct: u32,
1634 gap_range: Range<u64>,
1635 rng: &mut impl rand::Rng,
1636 ) {
1637 match self {
1638 Self::BlobsOnly(_) => {}
1639 Self::Mixed(set) => set.with_nonce_gaps(gap_pct, gap_range, rng),
1640 }
1641 }
1642}
1643
1644#[derive(Debug, Clone)]
1646pub struct MockTransactionSet {
1647 pub(crate) transactions: Vec<MockTransaction>,
1648}
1649
1650impl MockTransactionSet {
1651 const fn new(transactions: Vec<MockTransaction>) -> Self {
1653 Self { transactions }
1654 }
1655
1656 pub fn dependent(sender: Address, from_nonce: u64, tx_count: usize, tx_type: TxType) -> Self {
1663 let mut txs = Vec::with_capacity(tx_count);
1664 let mut curr_tx =
1665 MockTransaction::new_from_type(tx_type).with_nonce(from_nonce).with_sender(sender);
1666 for _ in 0..tx_count {
1667 txs.push(curr_tx.clone());
1668 curr_tx = curr_tx.next();
1669 }
1670
1671 Self::new(txs)
1672 }
1673
1674 pub fn sequential_transactions_by_sender(
1679 sender: Address,
1680 tx_count: usize,
1681 tx_type: TxType,
1682 ) -> Self {
1683 Self::dependent(sender, 0, tx_count, tx_type)
1684 }
1685
1686 pub fn with_nonce_gaps(
1700 &mut self,
1701 gap_pct: u32,
1702 gap_range: Range<u64>,
1703 rng: &mut impl rand::Rng,
1704 ) {
1705 assert!(gap_pct <= 100, "gap_pct must be between 0 and 100");
1706 assert!(gap_range.start >= 1, "gap_range must have a lower bound of at least one");
1707
1708 let mut prev_nonce = 0;
1709 for tx in &mut self.transactions {
1710 if rng.random_bool(gap_pct as f64 / 100.0) {
1711 prev_nonce += gap_range.start;
1712 } else {
1713 prev_nonce += 1;
1714 }
1715 tx.set_nonce(prev_nonce);
1716 }
1717 }
1718
1719 pub fn extend<T: IntoIterator<Item = MockTransaction>>(&mut self, txs: T) {
1721 self.transactions.extend(txs);
1722 }
1723
1724 pub fn into_vec(self) -> Vec<MockTransaction> {
1726 self.transactions
1727 }
1728
1729 pub fn iter(&self) -> impl Iterator<Item = &MockTransaction> {
1731 self.transactions.iter()
1732 }
1733
1734 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MockTransaction> {
1736 self.transactions.iter_mut()
1737 }
1738}
1739
1740impl IntoIterator for MockTransactionSet {
1741 type Item = MockTransaction;
1742 type IntoIter = IntoIter<MockTransaction>;
1743
1744 fn into_iter(self) -> Self::IntoIter {
1745 self.transactions.into_iter()
1746 }
1747}
1748
1749#[test]
1750fn test_mock_priority() {
1751 use crate::TransactionOrdering;
1752
1753 let o = MockOrdering::default();
1754 let lo = MockTransaction::eip1559().with_gas_limit(100_000);
1755 let hi = lo.next().inc_price();
1756 assert!(o.priority(&hi, 0) > o.priority(&lo, 0));
1757}
1758
1759#[cfg(test)]
1760mod tests {
1761 use super::*;
1762 use alloy_consensus::Transaction;
1763 use alloy_primitives::U256;
1764
1765 #[test]
1766 fn test_mock_transaction_factory() {
1767 let mut factory = MockTransactionFactory::default();
1768
1769 let legacy = factory.create_legacy();
1771 assert_eq!(legacy.transaction.tx_type(), TxType::Legacy);
1772
1773 let eip1559 = factory.create_eip1559();
1775 assert_eq!(eip1559.transaction.tx_type(), TxType::Eip1559);
1776
1777 let eip4844 = factory.create_eip4844();
1779 assert_eq!(eip4844.transaction.tx_type(), TxType::Eip4844);
1780 }
1781
1782 #[test]
1783 fn test_mock_transaction_set() {
1784 let sender = Address::random();
1785 let nonce_start = 0u64;
1786 let count = 3;
1787
1788 let legacy_set = MockTransactionSet::dependent(sender, nonce_start, count, TxType::Legacy);
1790 assert_eq!(legacy_set.transactions.len(), count);
1791 for (idx, tx) in legacy_set.transactions.iter().enumerate() {
1792 assert_eq!(tx.tx_type(), TxType::Legacy);
1793 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1794 assert_eq!(tx.sender(), sender);
1795 }
1796
1797 let eip1559_set =
1799 MockTransactionSet::dependent(sender, nonce_start, count, TxType::Eip1559);
1800 assert_eq!(eip1559_set.transactions.len(), count);
1801 for (idx, tx) in eip1559_set.transactions.iter().enumerate() {
1802 assert_eq!(tx.tx_type(), TxType::Eip1559);
1803 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1804 assert_eq!(tx.sender(), sender);
1805 }
1806 }
1807
1808 #[test]
1809 fn test_mock_transaction_modifications() {
1810 let tx = MockTransaction::eip1559();
1811
1812 let original_price = tx.get_gas_price();
1814 let tx_inc = tx.inc_price();
1815 assert!(tx_inc.get_gas_price() > original_price);
1816
1817 let original_limit = tx.gas_limit();
1819 let tx_inc = tx.inc_limit();
1820 assert!(tx_inc.gas_limit() > original_limit);
1821
1822 let original_nonce = tx.nonce();
1824 let tx_inc = tx.inc_nonce();
1825 assert_eq!(tx_inc.nonce(), original_nonce + 1);
1826 }
1827
1828 #[test]
1829 fn test_mock_transaction_cost() {
1830 let tx = MockTransaction::eip1559()
1831 .with_gas_limit(7_000)
1832 .with_max_fee(100)
1833 .with_value(U256::ZERO);
1834
1835 let expected_cost = U256::from(7_000u64) * U256::from(100u128) + U256::ZERO;
1837 assert_eq!(*tx.cost(), expected_cost);
1838 }
1839}