1use crate::TransactionType;
3use context_interface::{
4 either::Either,
5 transaction::{
6 AccessList, AccessListItem, Authorization, RecoveredAuthority, RecoveredAuthorization,
7 SignedAuthorization, Transaction,
8 },
9};
10use core::fmt::Debug;
11use database_interface::{BENCH_CALLER, BENCH_TARGET};
12use primitives::{eip7825, Address, Bytes, TxKind, B256, U256};
13use std::{vec, vec::Vec};
14
15#[derive(Clone, Debug, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct TxEnv {
23 pub tx_type: u8,
25 pub caller: Address,
27 pub gas_limit: u64,
29 pub gas_price: u128,
33 pub kind: TxKind,
35 pub value: U256,
37 pub data: Bytes,
39
40 pub nonce: u64,
42
43 pub chain_id: Option<u64>,
49
50 pub access_list: AccessList,
56
57 pub gas_priority_fee: Option<u128>,
63
64 pub blob_hashes: Vec<B256>,
72
73 pub max_fee_per_blob_gas: u128,
79
80 pub authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
89}
90
91impl Default for TxEnv {
92 fn default() -> Self {
93 Self::builder().build().unwrap()
94 }
95}
96
97#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100pub enum DeriveTxTypeError {
101 MissingTargetForEip4844,
103 MissingTargetForEip7702,
105 MissingTargetForEip7873,
107}
108
109impl TxEnv {
110 pub fn new_bench() -> Self {
112 Self {
113 caller: BENCH_CALLER,
114 kind: TxKind::Call(BENCH_TARGET),
115 gas_limit: 1_000_000_000,
116 ..Default::default()
117 }
118 }
119
120 pub fn derive_tx_type(&mut self) -> Result<(), DeriveTxTypeError> {
123 if !self.access_list.0.is_empty() {
124 self.tx_type = TransactionType::Eip2930 as u8;
125 }
126
127 if self.gas_priority_fee.is_some() {
128 self.tx_type = TransactionType::Eip1559 as u8;
129 }
130
131 if !self.blob_hashes.is_empty() || self.max_fee_per_blob_gas > 0 {
132 if let TxKind::Call(_) = self.kind {
133 self.tx_type = TransactionType::Eip4844 as u8;
134 return Ok(());
135 } else {
136 return Err(DeriveTxTypeError::MissingTargetForEip4844);
137 }
138 }
139
140 if !self.authorization_list.is_empty() {
141 if let TxKind::Call(_) = self.kind {
142 self.tx_type = TransactionType::Eip7702 as u8;
143 return Ok(());
144 } else {
145 return Err(DeriveTxTypeError::MissingTargetForEip7702);
146 }
147 }
148 Ok(())
149 }
150
151 pub fn set_signed_authorization(&mut self, auth: Vec<SignedAuthorization>) {
153 self.authorization_list = auth.into_iter().map(Either::Left).collect();
154 }
155
156 pub fn set_recovered_authorization(&mut self, auth: Vec<RecoveredAuthorization>) {
158 self.authorization_list = auth.into_iter().map(Either::Right).collect();
159 }
160}
161
162impl Transaction for TxEnv {
163 type AccessListItem<'a> = &'a AccessListItem;
164 type Authorization<'a> = &'a Either<SignedAuthorization, RecoveredAuthorization>;
165
166 fn tx_type(&self) -> u8 {
167 self.tx_type
168 }
169
170 fn kind(&self) -> TxKind {
171 self.kind
172 }
173
174 fn caller(&self) -> Address {
175 self.caller
176 }
177
178 fn gas_limit(&self) -> u64 {
179 self.gas_limit
180 }
181
182 fn gas_price(&self) -> u128 {
183 self.gas_price
184 }
185
186 fn value(&self) -> U256 {
187 self.value
188 }
189
190 fn nonce(&self) -> u64 {
191 self.nonce
192 }
193
194 fn chain_id(&self) -> Option<u64> {
195 self.chain_id
196 }
197
198 fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
199 Some(self.access_list.0.iter())
200 }
201
202 fn max_fee_per_gas(&self) -> u128 {
203 self.gas_price
204 }
205
206 fn max_fee_per_blob_gas(&self) -> u128 {
207 self.max_fee_per_blob_gas
208 }
209
210 fn authorization_list_len(&self) -> usize {
211 self.authorization_list.len()
212 }
213
214 fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
215 self.authorization_list.iter()
216 }
217
218 fn input(&self) -> &Bytes {
219 &self.data
220 }
221
222 fn blob_versioned_hashes(&self) -> &[B256] {
223 &self.blob_hashes
224 }
225
226 fn max_priority_fee_per_gas(&self) -> Option<u128> {
227 self.gas_priority_fee
228 }
229}
230
231#[derive(Default, Debug)]
233pub struct TxEnvBuilder {
234 tx_type: Option<u8>,
235 caller: Address,
236 gas_limit: u64,
237 gas_price: u128,
238 kind: TxKind,
239 value: U256,
240 data: Bytes,
241 nonce: u64,
242 chain_id: Option<u64>,
243 access_list: AccessList,
244 gas_priority_fee: Option<u128>,
245 blob_hashes: Vec<B256>,
246 max_fee_per_blob_gas: u128,
247 authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
248}
249
250impl TxEnvBuilder {
251 pub fn new() -> Self {
253 Self {
254 tx_type: None,
255 caller: Address::default(),
256 gas_limit: eip7825::TX_GAS_LIMIT_CAP,
257 gas_price: 0,
258 kind: TxKind::Call(Address::default()),
259 value: U256::ZERO,
260 data: Bytes::default(),
261 nonce: 0,
262 chain_id: Some(1), access_list: Default::default(),
264 gas_priority_fee: None,
265 blob_hashes: Vec::new(),
266 max_fee_per_blob_gas: 0,
267 authorization_list: Vec::new(),
268 }
269 }
270
271 pub fn tx_type(mut self, tx_type: Option<u8>) -> Self {
273 self.tx_type = tx_type;
274 self
275 }
276
277 pub fn get_tx_type(&self) -> Option<u8> {
279 self.tx_type
280 }
281
282 pub fn caller(mut self, caller: Address) -> Self {
284 self.caller = caller;
285 self
286 }
287
288 pub fn gas_limit(mut self, gas_limit: u64) -> Self {
290 self.gas_limit = gas_limit;
291 self
292 }
293
294 pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
296 self.gas_price = max_fee_per_gas;
297 self
298 }
299
300 pub fn gas_price(mut self, gas_price: u128) -> Self {
302 self.gas_price = gas_price;
303 self
304 }
305
306 pub fn kind(mut self, kind: TxKind) -> Self {
308 self.kind = kind;
309 self
310 }
311
312 pub fn call(mut self, target: Address) -> Self {
314 self.kind = TxKind::Call(target);
315 self
316 }
317
318 pub fn create(mut self) -> Self {
320 self.kind = TxKind::Create;
321 self
322 }
323
324 pub fn to(self, target: Address) -> Self {
326 self.call(target)
327 }
328
329 pub fn value(mut self, value: U256) -> Self {
331 self.value = value;
332 self
333 }
334
335 pub fn data(mut self, data: Bytes) -> Self {
337 self.data = data;
338 self
339 }
340
341 pub fn nonce(mut self, nonce: u64) -> Self {
343 self.nonce = nonce;
344 self
345 }
346
347 pub fn chain_id(mut self, chain_id: Option<u64>) -> Self {
349 self.chain_id = chain_id;
350 self
351 }
352
353 pub fn access_list(mut self, access_list: AccessList) -> Self {
355 self.access_list = access_list;
356 self
357 }
358
359 pub fn gas_priority_fee(mut self, gas_priority_fee: Option<u128>) -> Self {
361 self.gas_priority_fee = gas_priority_fee;
362 self
363 }
364
365 pub fn blob_hashes(mut self, blob_hashes: Vec<B256>) -> Self {
367 self.blob_hashes = blob_hashes;
368 self
369 }
370
371 pub fn max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self {
373 self.max_fee_per_blob_gas = max_fee_per_blob_gas;
374 self
375 }
376
377 pub fn authorization_list(
379 mut self,
380 authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
381 ) -> Self {
382 self.authorization_list = authorization_list;
383 self
384 }
385
386 pub fn authorization_list_signed(mut self, auth: Vec<SignedAuthorization>) -> Self {
388 self.authorization_list = auth.into_iter().map(Either::Left).collect();
389 self
390 }
391
392 pub fn authorization_list_recovered(mut self, auth: Vec<RecoveredAuthorization>) -> Self {
394 self.authorization_list = auth.into_iter().map(Either::Right).collect();
395 self
396 }
397
398 pub fn build_fill(mut self) -> TxEnv {
400 if let Some(tx_type) = self.tx_type {
401 match TransactionType::from(tx_type) {
402 TransactionType::Legacy => {
403 }
405 TransactionType::Eip2930 => {
406 }
408 TransactionType::Eip1559 => {
409 if self.gas_priority_fee.is_none() {
411 self.gas_priority_fee = Some(0);
412 }
413 }
414 TransactionType::Eip4844 => {
415 if self.gas_priority_fee.is_none() {
417 self.gas_priority_fee = Some(0);
418 }
419
420 if self.blob_hashes.is_empty() {
422 self.blob_hashes = vec![B256::default()];
423 }
424
425 if !self.kind.is_call() {
427 self.kind = TxKind::Call(Address::default());
428 }
429 }
430 TransactionType::Eip7702 => {
431 if self.gas_priority_fee.is_none() {
433 self.gas_priority_fee = Some(0);
434 }
435
436 if self.authorization_list.is_empty() {
438 self.authorization_list =
440 vec![Either::Right(RecoveredAuthorization::new_unchecked(
441 Authorization {
442 chain_id: U256::from(self.chain_id.unwrap_or(1)),
443 address: self.caller,
444 nonce: self.nonce,
445 },
446 RecoveredAuthority::Invalid,
447 ))];
448 }
449
450 if !self.kind.is_call() {
452 self.kind = TxKind::Call(Address::default());
453 }
454 }
455 TransactionType::Custom => {
456 }
458 }
459 }
460
461 let mut tx = TxEnv {
462 tx_type: self.tx_type.unwrap_or(0),
463 caller: self.caller,
464 gas_limit: self.gas_limit,
465 gas_price: self.gas_price,
466 kind: self.kind,
467 value: self.value,
468 data: self.data,
469 nonce: self.nonce,
470 chain_id: self.chain_id,
471 access_list: self.access_list,
472 gas_priority_fee: self.gas_priority_fee,
473 blob_hashes: self.blob_hashes,
474 max_fee_per_blob_gas: self.max_fee_per_blob_gas,
475 authorization_list: self.authorization_list,
476 };
477
478 if self.tx_type.is_none() {
480 match tx.derive_tx_type() {
481 Ok(_) => {}
482 Err(DeriveTxTypeError::MissingTargetForEip4844) => {
483 tx.kind = TxKind::Call(Address::default());
484 }
485 Err(DeriveTxTypeError::MissingTargetForEip7702) => {
486 tx.kind = TxKind::Call(Address::default());
487 }
488 Err(DeriveTxTypeError::MissingTargetForEip7873) => {
489 tx.kind = TxKind::Call(Address::default());
490 }
491 }
492 }
493
494 tx
495 }
496
497 pub fn build(self) -> Result<TxEnv, TxEnvBuildError> {
500 if let Some(tx_type) = self.tx_type {
502 match TransactionType::from(tx_type) {
503 TransactionType::Legacy => {
504 }
506 TransactionType::Eip2930 => {
507 }
509 TransactionType::Eip1559 => {
510 if self.gas_priority_fee.is_none() {
512 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
513 }
514 }
515 TransactionType::Eip4844 => {
516 if self.gas_priority_fee.is_none() {
518 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
519 }
520
521 if self.blob_hashes.is_empty() {
523 return Err(TxEnvBuildError::MissingBlobHashesForEip4844);
524 }
525
526 if !self.kind.is_call() {
528 return Err(TxEnvBuildError::MissingTargetForEip4844);
529 }
530 }
531 TransactionType::Eip7702 => {
532 if self.gas_priority_fee.is_none() {
534 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
535 }
536
537 if self.authorization_list.is_empty() {
539 return Err(TxEnvBuildError::MissingAuthorizationListForEip7702);
540 }
541
542 if !self.kind.is_call() {
544 return Err(DeriveTxTypeError::MissingTargetForEip4844.into());
545 }
546 }
547 TransactionType::Custom => {
548 }
550 }
551 }
552
553 let mut tx = TxEnv {
554 tx_type: self.tx_type.unwrap_or(0),
555 caller: self.caller,
556 gas_limit: self.gas_limit,
557 gas_price: self.gas_price,
558 kind: self.kind,
559 value: self.value,
560 data: self.data,
561 nonce: self.nonce,
562 chain_id: self.chain_id,
563 access_list: self.access_list,
564 gas_priority_fee: self.gas_priority_fee,
565 blob_hashes: self.blob_hashes,
566 max_fee_per_blob_gas: self.max_fee_per_blob_gas,
567 authorization_list: self.authorization_list,
568 };
569
570 if self.tx_type.is_none() {
572 tx.derive_tx_type()?;
573 }
574
575 Ok(tx)
576 }
577}
578
579#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
581#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
582pub enum TxEnvBuildError {
583 DeriveErr(DeriveTxTypeError),
585 MissingGasPriorityFeeForEip1559,
587 MissingBlobHashesForEip4844,
589 MissingAuthorizationListForEip7702,
591 MissingTargetForEip4844,
593}
594
595impl From<DeriveTxTypeError> for TxEnvBuildError {
596 fn from(error: DeriveTxTypeError) -> Self {
597 TxEnvBuildError::DeriveErr(error)
598 }
599}
600
601impl TxEnv {
602 pub fn builder() -> TxEnvBuilder {
604 TxEnvBuilder::new()
605 }
606
607 pub fn builder_for_bench() -> TxEnvBuilder {
609 TxEnv::new_bench().modify()
610 }
611
612 pub fn modify(self) -> TxEnvBuilder {
614 let TxEnv {
615 tx_type,
616 caller,
617 gas_limit,
618 gas_price,
619 kind,
620 value,
621 data,
622 nonce,
623 chain_id,
624 access_list,
625 gas_priority_fee,
626 blob_hashes,
627 max_fee_per_blob_gas,
628 authorization_list,
629 } = self;
630
631 TxEnvBuilder::new()
632 .tx_type(Some(tx_type))
633 .caller(caller)
634 .gas_limit(gas_limit)
635 .gas_price(gas_price)
636 .kind(kind)
637 .value(value)
638 .data(data)
639 .nonce(nonce)
640 .chain_id(chain_id)
641 .access_list(access_list)
642 .gas_priority_fee(gas_priority_fee)
643 .blob_hashes(blob_hashes)
644 .max_fee_per_blob_gas(max_fee_per_blob_gas)
645 .authorization_list(authorization_list)
646 }
647}
648
649#[cfg(test)]
650mod tests {
651 use super::*;
652
653 fn effective_gas_setup(
654 tx_type: TransactionType,
655 gas_price: u128,
656 gas_priority_fee: Option<u128>,
657 ) -> u128 {
658 let tx = TxEnv {
659 tx_type: tx_type as u8,
660 gas_price,
661 gas_priority_fee,
662 ..Default::default()
663 };
664 let base_fee = 100;
665 tx.effective_gas_price(base_fee)
666 }
667
668 #[test]
669 fn test_tx_env_builder_build_valid_legacy() {
670 let tx = TxEnvBuilder::new()
672 .tx_type(Some(0))
673 .caller(Address::from([1u8; 20]))
674 .gas_limit(21000)
675 .gas_price(20)
676 .kind(TxKind::Call(Address::from([2u8; 20])))
677 .value(U256::from(100))
678 .data(Bytes::from(vec![0x01, 0x02]))
679 .nonce(5)
680 .chain_id(Some(1))
681 .build()
682 .unwrap();
683
684 assert_eq!(tx.kind, TxKind::Call(Address::from([2u8; 20])));
685 assert_eq!(tx.caller, Address::from([1u8; 20]));
686 assert_eq!(tx.gas_limit, 21000);
687 assert_eq!(tx.gas_price, 20);
688 assert_eq!(tx.value, U256::from(100));
689 assert_eq!(tx.data, Bytes::from(vec![0x01, 0x02]));
690 assert_eq!(tx.nonce, 5);
691 assert_eq!(tx.chain_id, Some(1));
692 assert_eq!(tx.tx_type, TransactionType::Legacy);
693 }
694
695 #[test]
696 fn test_tx_env_builder_build_valid_eip2930() {
697 let access_list = AccessList(vec![AccessListItem {
699 address: Address::from([3u8; 20]),
700 storage_keys: vec![B256::from([4u8; 32])],
701 }]);
702 let tx = TxEnvBuilder::new()
703 .tx_type(Some(1))
704 .caller(Address::from([1u8; 20]))
705 .gas_limit(50000)
706 .gas_price(25)
707 .kind(TxKind::Call(Address::from([2u8; 20])))
708 .access_list(access_list.clone())
709 .build()
710 .unwrap();
711
712 assert_eq!(tx.tx_type, TransactionType::Eip2930);
713 assert_eq!(tx.access_list, access_list);
714 }
715
716 #[test]
717 fn test_tx_env_builder_build_valid_eip1559() {
718 let tx = TxEnvBuilder::new()
720 .tx_type(Some(2))
721 .caller(Address::from([1u8; 20]))
722 .gas_limit(50000)
723 .gas_price(30)
724 .gas_priority_fee(Some(10))
725 .kind(TxKind::Call(Address::from([2u8; 20])))
726 .build()
727 .unwrap();
728
729 assert_eq!(tx.tx_type, TransactionType::Eip1559);
730 assert_eq!(tx.gas_priority_fee, Some(10));
731 }
732
733 #[test]
734 fn test_tx_env_builder_build_valid_eip4844() {
735 let blob_hashes = vec![B256::from([5u8; 32]), B256::from([6u8; 32])];
737 let tx = TxEnvBuilder::new()
738 .tx_type(Some(3))
739 .caller(Address::from([1u8; 20]))
740 .gas_limit(50000)
741 .gas_price(30)
742 .gas_priority_fee(Some(10))
743 .kind(TxKind::Call(Address::from([2u8; 20])))
744 .blob_hashes(blob_hashes.clone())
745 .max_fee_per_blob_gas(100)
746 .build()
747 .unwrap();
748
749 assert_eq!(tx.tx_type, TransactionType::Eip4844);
750 assert_eq!(tx.blob_hashes, blob_hashes);
751 assert_eq!(tx.max_fee_per_blob_gas, 100);
752 }
753
754 #[test]
755 fn test_tx_env_builder_build_valid_eip7702() {
756 let auth = RecoveredAuthorization::new_unchecked(
758 Authorization {
759 chain_id: U256::from(1),
760 nonce: 0,
761 address: Address::default(),
762 },
763 RecoveredAuthority::Valid(Address::default()),
764 );
765 let auth_list = vec![Either::Right(auth)];
766
767 let tx = TxEnvBuilder::new()
768 .tx_type(Some(4))
769 .caller(Address::from([1u8; 20]))
770 .gas_limit(50000)
771 .gas_price(30)
772 .gas_priority_fee(Some(10))
773 .kind(TxKind::Call(Address::from([2u8; 20])))
774 .authorization_list(auth_list.clone())
775 .build()
776 .unwrap();
777
778 assert_eq!(tx.tx_type, TransactionType::Eip7702);
779 assert_eq!(tx.authorization_list.len(), 1);
780 }
781
782 #[test]
783 fn test_tx_env_builder_build_create_transaction() {
784 let bytecode = Bytes::from(vec![0x60, 0x80, 0x60, 0x40]);
786 let tx = TxEnvBuilder::new()
787 .kind(TxKind::Create)
788 .data(bytecode.clone())
789 .gas_limit(100000)
790 .gas_price(20)
791 .build()
792 .unwrap();
793
794 assert_eq!(tx.kind, TxKind::Create);
795 assert_eq!(tx.data, bytecode);
796 }
797
798 #[test]
799 fn test_tx_env_builder_build_errors_eip1559_missing_priority_fee() {
800 let result = TxEnvBuilder::new()
802 .tx_type(Some(2))
803 .caller(Address::from([1u8; 20]))
804 .gas_limit(50000)
805 .gas_price(30)
806 .kind(TxKind::Call(Address::from([2u8; 20])))
807 .build();
808
809 assert!(matches!(
810 result,
811 Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559)
812 ));
813 }
814
815 #[test]
816 fn test_tx_env_builder_build_errors_eip4844_missing_blob_hashes() {
817 let result = TxEnvBuilder::new()
819 .tx_type(Some(3))
820 .gas_priority_fee(Some(10))
821 .kind(TxKind::Call(Address::from([2u8; 20])))
822 .build();
823
824 assert!(matches!(
825 result,
826 Err(TxEnvBuildError::MissingBlobHashesForEip4844)
827 ));
828 }
829
830 #[test]
831 fn test_tx_env_builder_build_errors_eip4844_not_call() {
832 let result = TxEnvBuilder::new()
834 .tx_type(Some(3))
835 .gas_priority_fee(Some(10))
836 .blob_hashes(vec![B256::from([5u8; 32])])
837 .kind(TxKind::Create)
838 .build();
839
840 assert!(matches!(
841 result,
842 Err(TxEnvBuildError::MissingTargetForEip4844)
843 ));
844 }
845
846 #[test]
847 fn test_tx_env_builder_build_errors_eip7702_missing_auth_list() {
848 let result = TxEnvBuilder::new()
850 .tx_type(Some(4))
851 .gas_priority_fee(Some(10))
852 .kind(TxKind::Call(Address::from([2u8; 20])))
853 .build();
854
855 assert!(matches!(
856 result,
857 Err(TxEnvBuildError::MissingAuthorizationListForEip7702)
858 ));
859 }
860
861 #[test]
862 fn test_tx_env_builder_build_errors_eip7702_not_call() {
863 let auth = RecoveredAuthorization::new_unchecked(
865 Authorization {
866 chain_id: U256::from(1),
867 nonce: 0,
868 address: Address::default(),
869 },
870 RecoveredAuthority::Valid(Address::default()),
871 );
872 let result = TxEnvBuilder::new()
873 .tx_type(Some(4))
874 .gas_priority_fee(Some(10))
875 .authorization_list(vec![Either::Right(auth)])
876 .kind(TxKind::Create)
877 .build();
878
879 assert!(matches!(result, Err(TxEnvBuildError::DeriveErr(_))));
880 }
881
882 #[test]
883 fn test_tx_env_builder_build_fill_legacy() {
884 let tx = TxEnvBuilder::new()
886 .caller(Address::from([1u8; 20]))
887 .gas_limit(21000)
888 .gas_price(20)
889 .kind(TxKind::Call(Address::from([2u8; 20])))
890 .build_fill();
891
892 assert_eq!(tx.tx_type, TransactionType::Legacy);
893 assert_eq!(tx.gas_priority_fee, None);
894 }
895
896 #[test]
897 fn test_tx_env_builder_build_fill_eip1559_missing_priority_fee() {
898 let tx = TxEnvBuilder::new()
900 .tx_type(Some(2))
901 .caller(Address::from([1u8; 20]))
902 .gas_limit(50000)
903 .gas_price(30)
904 .kind(TxKind::Call(Address::from([2u8; 20])))
905 .build_fill();
906
907 assert_eq!(tx.tx_type, TransactionType::Eip1559);
908 assert_eq!(tx.gas_priority_fee, Some(0));
909 }
910
911 #[test]
912 fn test_tx_env_builder_build_fill_eip4844_missing_blob_hashes() {
913 let tx = TxEnvBuilder::new()
915 .tx_type(Some(3))
916 .gas_priority_fee(Some(10))
917 .kind(TxKind::Call(Address::from([2u8; 20])))
918 .build_fill();
919
920 assert_eq!(tx.tx_type, TransactionType::Eip4844);
921 assert_eq!(tx.blob_hashes.len(), 1);
922 assert_eq!(tx.blob_hashes[0], B256::default());
923 }
924
925 #[test]
926 fn test_tx_env_builder_build_fill_eip4844_create_to_call() {
927 let tx = TxEnvBuilder::new()
929 .tx_type(Some(3))
930 .gas_priority_fee(Some(10))
931 .blob_hashes(vec![B256::from([5u8; 32])])
932 .kind(TxKind::Create)
933 .build_fill();
934
935 assert_eq!(tx.tx_type, TransactionType::Eip4844);
936 assert_eq!(tx.kind, TxKind::Call(Address::default()));
937 }
938
939 #[test]
940 fn test_tx_env_builder_build_fill_eip7702_missing_auth_list() {
941 let tx = TxEnvBuilder::new()
943 .tx_type(Some(4))
944 .gas_priority_fee(Some(10))
945 .kind(TxKind::Call(Address::from([2u8; 20])))
946 .build_fill();
947
948 assert_eq!(tx.tx_type, TransactionType::Eip7702);
949 assert_eq!(tx.authorization_list.len(), 1);
950 }
951
952 #[test]
953 fn test_tx_env_builder_build_fill_eip7702_create_to_call() {
954 let auth = RecoveredAuthorization::new_unchecked(
956 Authorization {
957 chain_id: U256::from(1),
958 nonce: 0,
959 address: Address::default(),
960 },
961 RecoveredAuthority::Valid(Address::default()),
962 );
963 let tx = TxEnvBuilder::new()
964 .tx_type(Some(4))
965 .gas_priority_fee(Some(10))
966 .authorization_list(vec![Either::Right(auth)])
967 .kind(TxKind::Create)
968 .build_fill();
969
970 assert_eq!(tx.tx_type, TransactionType::Eip7702);
971 assert_eq!(tx.kind, TxKind::Call(Address::default()));
972 }
973
974 #[test]
975 fn test_tx_env_builder_derive_tx_type_legacy() {
976 let tx = TxEnvBuilder::new()
978 .caller(Address::from([1u8; 20]))
979 .gas_limit(21000)
980 .gas_price(20)
981 .build()
982 .unwrap();
983
984 assert_eq!(tx.tx_type, TransactionType::Legacy);
985 }
986
987 #[test]
988 fn test_tx_env_builder_derive_tx_type_eip2930() {
989 let access_list = AccessList(vec![AccessListItem {
991 address: Address::from([3u8; 20]),
992 storage_keys: vec![B256::from([4u8; 32])],
993 }]);
994 let tx = TxEnvBuilder::new()
995 .caller(Address::from([1u8; 20]))
996 .access_list(access_list)
997 .build()
998 .unwrap();
999
1000 assert_eq!(tx.tx_type, TransactionType::Eip2930);
1001 }
1002
1003 #[test]
1004 fn test_tx_env_builder_derive_tx_type_eip1559() {
1005 let tx = TxEnvBuilder::new()
1007 .caller(Address::from([1u8; 20]))
1008 .gas_priority_fee(Some(10))
1009 .build()
1010 .unwrap();
1011
1012 assert_eq!(tx.tx_type, TransactionType::Eip1559);
1013 }
1014
1015 #[test]
1016 fn test_tx_env_builder_derive_tx_type_eip4844() {
1017 let tx = TxEnvBuilder::new()
1019 .caller(Address::from([1u8; 20]))
1020 .gas_priority_fee(Some(10))
1021 .blob_hashes(vec![B256::from([5u8; 32])])
1022 .kind(TxKind::Call(Address::from([2u8; 20])))
1023 .build()
1024 .unwrap();
1025
1026 assert_eq!(tx.tx_type, TransactionType::Eip4844);
1027 }
1028
1029 #[test]
1030 fn test_tx_env_builder_derive_tx_type_eip7702() {
1031 let auth = RecoveredAuthorization::new_unchecked(
1033 Authorization {
1034 chain_id: U256::from(1),
1035 nonce: 0,
1036 address: Address::default(),
1037 },
1038 RecoveredAuthority::Valid(Address::default()),
1039 );
1040 let tx = TxEnvBuilder::new()
1041 .caller(Address::from([1u8; 20]))
1042 .gas_priority_fee(Some(10))
1043 .authorization_list(vec![Either::Right(auth)])
1044 .kind(TxKind::Call(Address::from([2u8; 20])))
1045 .build()
1046 .unwrap();
1047
1048 assert_eq!(tx.tx_type, TransactionType::Eip7702);
1049 }
1050
1051 #[test]
1052 fn test_tx_env_builder_custom_tx_type() {
1053 let tx = TxEnvBuilder::new()
1055 .tx_type(Some(0xFF))
1056 .caller(Address::from([1u8; 20]))
1057 .build()
1058 .unwrap();
1059
1060 assert_eq!(tx.tx_type, TransactionType::Custom);
1061 }
1062
1063 #[test]
1064 fn test_tx_env_builder_chain_methods() {
1065 let tx = TxEnvBuilder::new()
1067 .caller(Address::from([1u8; 20]))
1068 .gas_limit(50000)
1069 .gas_price(25)
1070 .kind(TxKind::Call(Address::from([2u8; 20])))
1071 .value(U256::from(1000))
1072 .data(Bytes::from(vec![0x12, 0x34]))
1073 .nonce(10)
1074 .chain_id(Some(5))
1075 .access_list(AccessList(vec![AccessListItem {
1076 address: Address::from([3u8; 20]),
1077 storage_keys: vec![],
1078 }]))
1079 .gas_priority_fee(Some(5))
1080 .blob_hashes(vec![B256::from([7u8; 32])])
1081 .max_fee_per_blob_gas(200)
1082 .build_fill();
1083
1084 assert_eq!(tx.caller, Address::from([1u8; 20]));
1085 assert_eq!(tx.gas_limit, 50000);
1086 assert_eq!(tx.gas_price, 25);
1087 assert_eq!(tx.kind, TxKind::Call(Address::from([2u8; 20])));
1088 assert_eq!(tx.value, U256::from(1000));
1089 assert_eq!(tx.data, Bytes::from(vec![0x12, 0x34]));
1090 assert_eq!(tx.nonce, 10);
1091 assert_eq!(tx.chain_id, Some(5));
1092 assert_eq!(tx.access_list.len(), 1);
1093 assert_eq!(tx.gas_priority_fee, Some(5));
1094 assert_eq!(tx.blob_hashes.len(), 1);
1095 assert_eq!(tx.max_fee_per_blob_gas, 200);
1096 }
1097
1098 #[test]
1099 fn test_effective_gas_price() {
1100 assert_eq!(90, effective_gas_setup(TransactionType::Legacy, 90, None));
1101 assert_eq!(
1102 90,
1103 effective_gas_setup(TransactionType::Legacy, 90, Some(0))
1104 );
1105 assert_eq!(
1106 90,
1107 effective_gas_setup(TransactionType::Legacy, 90, Some(10))
1108 );
1109 assert_eq!(
1110 120,
1111 effective_gas_setup(TransactionType::Legacy, 120, Some(10))
1112 );
1113 assert_eq!(90, effective_gas_setup(TransactionType::Eip2930, 90, None));
1114 assert_eq!(
1115 90,
1116 effective_gas_setup(TransactionType::Eip2930, 90, Some(0))
1117 );
1118 assert_eq!(
1119 90,
1120 effective_gas_setup(TransactionType::Eip2930, 90, Some(10))
1121 );
1122 assert_eq!(
1123 120,
1124 effective_gas_setup(TransactionType::Eip2930, 120, Some(10))
1125 );
1126 assert_eq!(90, effective_gas_setup(TransactionType::Eip1559, 90, None));
1127 assert_eq!(
1128 90,
1129 effective_gas_setup(TransactionType::Eip1559, 90, Some(0))
1130 );
1131 assert_eq!(
1132 90,
1133 effective_gas_setup(TransactionType::Eip1559, 90, Some(10))
1134 );
1135 assert_eq!(
1136 110,
1137 effective_gas_setup(TransactionType::Eip1559, 120, Some(10))
1138 );
1139 assert_eq!(90, effective_gas_setup(TransactionType::Eip4844, 90, None));
1140 assert_eq!(
1141 90,
1142 effective_gas_setup(TransactionType::Eip4844, 90, Some(0))
1143 );
1144 assert_eq!(
1145 90,
1146 effective_gas_setup(TransactionType::Eip4844, 90, Some(10))
1147 );
1148 assert_eq!(
1149 110,
1150 effective_gas_setup(TransactionType::Eip4844, 120, Some(10))
1151 );
1152 assert_eq!(90, effective_gas_setup(TransactionType::Eip7702, 90, None));
1153 assert_eq!(
1154 90,
1155 effective_gas_setup(TransactionType::Eip7702, 90, Some(0))
1156 );
1157 assert_eq!(
1158 90,
1159 effective_gas_setup(TransactionType::Eip7702, 90, Some(10))
1160 );
1161 assert_eq!(
1162 110,
1163 effective_gas_setup(TransactionType::Eip7702, 120, Some(10))
1164 );
1165 }
1166}