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 core::fmt::Display for DeriveTxTypeError {
110 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
111 let s = match self {
112 Self::MissingTargetForEip4844 => "missing target for EIP-4844",
113 Self::MissingTargetForEip7702 => "missing target for EIP-7702",
114 Self::MissingTargetForEip7873 => "missing target for EIP-7873",
115 };
116 f.write_str(s)
117 }
118}
119
120impl core::error::Error for DeriveTxTypeError {}
121
122impl TxEnv {
123 pub fn new_bench() -> Self {
125 Self {
126 caller: BENCH_CALLER,
127 kind: TxKind::Call(BENCH_TARGET),
128 gas_limit: 1_000_000_000,
129 ..Default::default()
130 }
131 }
132
133 pub fn derive_tx_type(&mut self) -> Result<(), DeriveTxTypeError> {
136 if !self.access_list.0.is_empty() {
137 self.tx_type = TransactionType::Eip2930 as u8;
138 }
139
140 if self.gas_priority_fee.is_some() {
141 self.tx_type = TransactionType::Eip1559 as u8;
142 }
143
144 if !self.blob_hashes.is_empty() || self.max_fee_per_blob_gas > 0 {
145 if let TxKind::Call(_) = self.kind {
146 self.tx_type = TransactionType::Eip4844 as u8;
147 return Ok(());
148 } else {
149 return Err(DeriveTxTypeError::MissingTargetForEip4844);
150 }
151 }
152
153 if !self.authorization_list.is_empty() {
154 if let TxKind::Call(_) = self.kind {
155 self.tx_type = TransactionType::Eip7702 as u8;
156 return Ok(());
157 } else {
158 return Err(DeriveTxTypeError::MissingTargetForEip7702);
159 }
160 }
161 Ok(())
162 }
163
164 pub fn set_signed_authorization(&mut self, auth: Vec<SignedAuthorization>) {
166 self.authorization_list = auth.into_iter().map(Either::Left).collect();
167 }
168
169 pub fn set_recovered_authorization(&mut self, auth: Vec<RecoveredAuthorization>) {
171 self.authorization_list = auth.into_iter().map(Either::Right).collect();
172 }
173}
174
175impl Transaction for TxEnv {
176 type AccessListItem<'a> = &'a AccessListItem;
177 type Authorization<'a> = &'a Either<SignedAuthorization, RecoveredAuthorization>;
178
179 fn tx_type(&self) -> u8 {
180 self.tx_type
181 }
182
183 fn kind(&self) -> TxKind {
184 self.kind
185 }
186
187 fn caller(&self) -> Address {
188 self.caller
189 }
190
191 fn gas_limit(&self) -> u64 {
192 self.gas_limit
193 }
194
195 fn gas_price(&self) -> u128 {
196 self.gas_price
197 }
198
199 fn value(&self) -> U256 {
200 self.value
201 }
202
203 fn nonce(&self) -> u64 {
204 self.nonce
205 }
206
207 fn chain_id(&self) -> Option<u64> {
208 self.chain_id
209 }
210
211 fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
212 Some(self.access_list.0.iter())
213 }
214
215 fn max_fee_per_gas(&self) -> u128 {
216 self.gas_price
217 }
218
219 fn max_fee_per_blob_gas(&self) -> u128 {
220 self.max_fee_per_blob_gas
221 }
222
223 fn authorization_list_len(&self) -> usize {
224 self.authorization_list.len()
225 }
226
227 fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
228 self.authorization_list.iter()
229 }
230
231 fn input(&self) -> &Bytes {
232 &self.data
233 }
234
235 fn blob_versioned_hashes(&self) -> &[B256] {
236 &self.blob_hashes
237 }
238
239 fn max_priority_fee_per_gas(&self) -> Option<u128> {
240 self.gas_priority_fee
241 }
242}
243
244#[derive(Default, Debug)]
246pub struct TxEnvBuilder {
247 tx_type: Option<u8>,
248 caller: Address,
249 gas_limit: u64,
250 gas_price: u128,
251 kind: TxKind,
252 value: U256,
253 data: Bytes,
254 nonce: u64,
255 chain_id: Option<u64>,
256 access_list: AccessList,
257 gas_priority_fee: Option<u128>,
258 blob_hashes: Vec<B256>,
259 max_fee_per_blob_gas: u128,
260 authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
261}
262
263impl TxEnvBuilder {
264 pub fn new() -> Self {
266 Self {
267 tx_type: None,
268 caller: Address::default(),
269 gas_limit: eip7825::TX_GAS_LIMIT_CAP,
270 gas_price: 0,
271 kind: TxKind::Call(Address::default()),
272 value: U256::ZERO,
273 data: Bytes::default(),
274 nonce: 0,
275 chain_id: Some(1), access_list: Default::default(),
277 gas_priority_fee: None,
278 blob_hashes: Vec::new(),
279 max_fee_per_blob_gas: 0,
280 authorization_list: Vec::new(),
281 }
282 }
283
284 pub fn tx_type(mut self, tx_type: Option<u8>) -> Self {
286 self.tx_type = tx_type;
287 self
288 }
289
290 pub fn get_tx_type(&self) -> Option<u8> {
292 self.tx_type
293 }
294
295 pub fn caller(mut self, caller: Address) -> Self {
297 self.caller = caller;
298 self
299 }
300
301 pub fn gas_limit(mut self, gas_limit: u64) -> Self {
303 self.gas_limit = gas_limit;
304 self
305 }
306
307 pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
309 self.gas_price = max_fee_per_gas;
310 self
311 }
312
313 pub fn gas_price(mut self, gas_price: u128) -> Self {
315 self.gas_price = gas_price;
316 self
317 }
318
319 pub fn kind(mut self, kind: TxKind) -> Self {
321 self.kind = kind;
322 self
323 }
324
325 pub fn call(mut self, target: Address) -> Self {
327 self.kind = TxKind::Call(target);
328 self
329 }
330
331 pub fn create(mut self) -> Self {
333 self.kind = TxKind::Create;
334 self
335 }
336
337 pub fn to(self, target: Address) -> Self {
339 self.call(target)
340 }
341
342 pub fn value(mut self, value: U256) -> Self {
344 self.value = value;
345 self
346 }
347
348 pub fn data(mut self, data: Bytes) -> Self {
350 self.data = data;
351 self
352 }
353
354 pub fn nonce(mut self, nonce: u64) -> Self {
356 self.nonce = nonce;
357 self
358 }
359
360 pub fn chain_id(mut self, chain_id: Option<u64>) -> Self {
362 self.chain_id = chain_id;
363 self
364 }
365
366 pub fn access_list(mut self, access_list: AccessList) -> Self {
368 self.access_list = access_list;
369 self
370 }
371
372 pub fn gas_priority_fee(mut self, gas_priority_fee: Option<u128>) -> Self {
374 self.gas_priority_fee = gas_priority_fee;
375 self
376 }
377
378 pub fn blob_hashes(mut self, blob_hashes: Vec<B256>) -> Self {
380 self.blob_hashes = blob_hashes;
381 self
382 }
383
384 pub fn max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self {
386 self.max_fee_per_blob_gas = max_fee_per_blob_gas;
387 self
388 }
389
390 pub fn authorization_list(
392 mut self,
393 authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
394 ) -> Self {
395 self.authorization_list = authorization_list;
396 self
397 }
398
399 pub fn authorization_list_signed(mut self, auth: Vec<SignedAuthorization>) -> Self {
401 self.authorization_list = auth.into_iter().map(Either::Left).collect();
402 self
403 }
404
405 pub fn authorization_list_recovered(mut self, auth: Vec<RecoveredAuthorization>) -> Self {
407 self.authorization_list = auth.into_iter().map(Either::Right).collect();
408 self
409 }
410
411 pub fn build_fill(mut self) -> TxEnv {
413 if let Some(tx_type) = self.tx_type {
414 match TransactionType::from(tx_type) {
415 TransactionType::Legacy => {
416 }
418 TransactionType::Eip2930 => {
419 }
421 TransactionType::Eip1559 => {
422 if self.gas_priority_fee.is_none() {
424 self.gas_priority_fee = Some(0);
425 }
426 }
427 TransactionType::Eip4844 => {
428 if self.gas_priority_fee.is_none() {
430 self.gas_priority_fee = Some(0);
431 }
432
433 if self.blob_hashes.is_empty() {
435 self.blob_hashes = vec![B256::default()];
436 }
437
438 if !self.kind.is_call() {
440 self.kind = TxKind::Call(Address::default());
441 }
442 }
443 TransactionType::Eip7702 => {
444 if self.gas_priority_fee.is_none() {
446 self.gas_priority_fee = Some(0);
447 }
448
449 if self.authorization_list.is_empty() {
451 self.authorization_list =
453 vec![Either::Right(RecoveredAuthorization::new_unchecked(
454 Authorization {
455 chain_id: U256::from(self.chain_id.unwrap_or(1)),
456 address: self.caller,
457 nonce: self.nonce,
458 },
459 RecoveredAuthority::Invalid,
460 ))];
461 }
462
463 if !self.kind.is_call() {
465 self.kind = TxKind::Call(Address::default());
466 }
467 }
468 TransactionType::Custom => {
469 }
471 }
472 }
473
474 let mut tx = TxEnv {
475 tx_type: self.tx_type.unwrap_or(0),
476 caller: self.caller,
477 gas_limit: self.gas_limit,
478 gas_price: self.gas_price,
479 kind: self.kind,
480 value: self.value,
481 data: self.data,
482 nonce: self.nonce,
483 chain_id: self.chain_id,
484 access_list: self.access_list,
485 gas_priority_fee: self.gas_priority_fee,
486 blob_hashes: self.blob_hashes,
487 max_fee_per_blob_gas: self.max_fee_per_blob_gas,
488 authorization_list: self.authorization_list,
489 };
490
491 if self.tx_type.is_none() {
493 match tx.derive_tx_type() {
494 Ok(_) => {}
495 Err(DeriveTxTypeError::MissingTargetForEip4844) => {
496 tx.kind = TxKind::Call(Address::default());
497 }
498 Err(DeriveTxTypeError::MissingTargetForEip7702) => {
499 tx.kind = TxKind::Call(Address::default());
500 }
501 Err(DeriveTxTypeError::MissingTargetForEip7873) => {
502 tx.kind = TxKind::Call(Address::default());
503 }
504 }
505 }
506
507 tx
508 }
509
510 pub fn build(self) -> Result<TxEnv, TxEnvBuildError> {
513 if let Some(tx_type) = self.tx_type {
515 match TransactionType::from(tx_type) {
516 TransactionType::Legacy => {
517 }
519 TransactionType::Eip2930 => {
520 }
522 TransactionType::Eip1559 => {
523 if self.gas_priority_fee.is_none() {
525 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
526 }
527 }
528 TransactionType::Eip4844 => {
529 if self.gas_priority_fee.is_none() {
531 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
532 }
533
534 if self.blob_hashes.is_empty() {
536 return Err(TxEnvBuildError::MissingBlobHashesForEip4844);
537 }
538
539 if !self.kind.is_call() {
541 return Err(TxEnvBuildError::MissingTargetForEip4844);
542 }
543 }
544 TransactionType::Eip7702 => {
545 if self.gas_priority_fee.is_none() {
547 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
548 }
549
550 if self.authorization_list.is_empty() {
552 return Err(TxEnvBuildError::MissingAuthorizationListForEip7702);
553 }
554
555 if !self.kind.is_call() {
557 return Err(DeriveTxTypeError::MissingTargetForEip7702.into());
558 }
559 }
560 TransactionType::Custom => {
561 }
563 }
564 }
565
566 let mut tx = TxEnv {
567 tx_type: self.tx_type.unwrap_or(0),
568 caller: self.caller,
569 gas_limit: self.gas_limit,
570 gas_price: self.gas_price,
571 kind: self.kind,
572 value: self.value,
573 data: self.data,
574 nonce: self.nonce,
575 chain_id: self.chain_id,
576 access_list: self.access_list,
577 gas_priority_fee: self.gas_priority_fee,
578 blob_hashes: self.blob_hashes,
579 max_fee_per_blob_gas: self.max_fee_per_blob_gas,
580 authorization_list: self.authorization_list,
581 };
582
583 if self.tx_type.is_none() {
585 tx.derive_tx_type()?;
586 }
587
588 Ok(tx)
589 }
590}
591
592#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
594#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
595pub enum TxEnvBuildError {
596 DeriveErr(DeriveTxTypeError),
598 MissingGasPriorityFeeForEip1559,
600 MissingBlobHashesForEip4844,
602 MissingAuthorizationListForEip7702,
604 MissingTargetForEip4844,
606}
607
608impl core::fmt::Display for TxEnvBuildError {
609 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
610 match self {
611 Self::DeriveErr(err) => write!(f, "derive tx type error: {err}"),
612 Self::MissingGasPriorityFeeForEip1559 => {
613 f.write_str("missing gas priority fee for EIP-1559")
614 }
615 Self::MissingBlobHashesForEip4844 => f.write_str("missing blob hashes for EIP-4844"),
616 Self::MissingAuthorizationListForEip7702 => {
617 f.write_str("missing authorization list for EIP-7702")
618 }
619 Self::MissingTargetForEip4844 => f.write_str("missing target for EIP-4844"),
620 }
621 }
622}
623
624impl core::error::Error for TxEnvBuildError {
625 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
626 match self {
627 Self::DeriveErr(err) => Some(err),
628 _ => None,
629 }
630 }
631}
632
633impl From<DeriveTxTypeError> for TxEnvBuildError {
634 fn from(error: DeriveTxTypeError) -> Self {
635 TxEnvBuildError::DeriveErr(error)
636 }
637}
638
639impl TxEnv {
640 pub fn builder() -> TxEnvBuilder {
642 TxEnvBuilder::new()
643 }
644
645 pub fn builder_for_bench() -> TxEnvBuilder {
647 TxEnv::new_bench().modify()
648 }
649
650 pub fn modify(self) -> TxEnvBuilder {
652 let TxEnv {
653 tx_type,
654 caller,
655 gas_limit,
656 gas_price,
657 kind,
658 value,
659 data,
660 nonce,
661 chain_id,
662 access_list,
663 gas_priority_fee,
664 blob_hashes,
665 max_fee_per_blob_gas,
666 authorization_list,
667 } = self;
668
669 TxEnvBuilder::new()
670 .tx_type(Some(tx_type))
671 .caller(caller)
672 .gas_limit(gas_limit)
673 .gas_price(gas_price)
674 .kind(kind)
675 .value(value)
676 .data(data)
677 .nonce(nonce)
678 .chain_id(chain_id)
679 .access_list(access_list)
680 .gas_priority_fee(gas_priority_fee)
681 .blob_hashes(blob_hashes)
682 .max_fee_per_blob_gas(max_fee_per_blob_gas)
683 .authorization_list(authorization_list)
684 }
685}
686
687#[cfg(test)]
688mod tests {
689 use super::*;
690
691 fn effective_gas_setup(
692 tx_type: TransactionType,
693 gas_price: u128,
694 gas_priority_fee: Option<u128>,
695 ) -> u128 {
696 let tx = TxEnv {
697 tx_type: tx_type as u8,
698 gas_price,
699 gas_priority_fee,
700 ..Default::default()
701 };
702 let base_fee = 100;
703 tx.effective_gas_price(base_fee)
704 }
705
706 #[test]
707 fn test_tx_env_builder_build_valid_legacy() {
708 let tx = TxEnvBuilder::new()
710 .tx_type(Some(0))
711 .caller(Address::from([1u8; 20]))
712 .gas_limit(21000)
713 .gas_price(20)
714 .kind(TxKind::Call(Address::from([2u8; 20])))
715 .value(U256::from(100))
716 .data(Bytes::from(vec![0x01, 0x02]))
717 .nonce(5)
718 .chain_id(Some(1))
719 .build()
720 .unwrap();
721
722 assert_eq!(tx.kind, TxKind::Call(Address::from([2u8; 20])));
723 assert_eq!(tx.caller, Address::from([1u8; 20]));
724 assert_eq!(tx.gas_limit, 21000);
725 assert_eq!(tx.gas_price, 20);
726 assert_eq!(tx.value, U256::from(100));
727 assert_eq!(tx.data, Bytes::from(vec![0x01, 0x02]));
728 assert_eq!(tx.nonce, 5);
729 assert_eq!(tx.chain_id, Some(1));
730 assert_eq!(tx.tx_type, TransactionType::Legacy);
731 }
732
733 #[test]
734 fn test_tx_env_builder_build_valid_eip2930() {
735 let access_list = AccessList(vec![AccessListItem {
737 address: Address::from([3u8; 20]),
738 storage_keys: vec![B256::from([4u8; 32])],
739 }]);
740 let tx = TxEnvBuilder::new()
741 .tx_type(Some(1))
742 .caller(Address::from([1u8; 20]))
743 .gas_limit(50000)
744 .gas_price(25)
745 .kind(TxKind::Call(Address::from([2u8; 20])))
746 .access_list(access_list.clone())
747 .build()
748 .unwrap();
749
750 assert_eq!(tx.tx_type, TransactionType::Eip2930);
751 assert_eq!(tx.access_list, access_list);
752 }
753
754 #[test]
755 fn test_tx_env_builder_build_valid_eip1559() {
756 let tx = TxEnvBuilder::new()
758 .tx_type(Some(2))
759 .caller(Address::from([1u8; 20]))
760 .gas_limit(50000)
761 .gas_price(30)
762 .gas_priority_fee(Some(10))
763 .kind(TxKind::Call(Address::from([2u8; 20])))
764 .build()
765 .unwrap();
766
767 assert_eq!(tx.tx_type, TransactionType::Eip1559);
768 assert_eq!(tx.gas_priority_fee, Some(10));
769 }
770
771 #[test]
772 fn test_tx_env_builder_build_valid_eip4844() {
773 let blob_hashes = vec![B256::from([5u8; 32]), B256::from([6u8; 32])];
775 let tx = TxEnvBuilder::new()
776 .tx_type(Some(3))
777 .caller(Address::from([1u8; 20]))
778 .gas_limit(50000)
779 .gas_price(30)
780 .gas_priority_fee(Some(10))
781 .kind(TxKind::Call(Address::from([2u8; 20])))
782 .blob_hashes(blob_hashes.clone())
783 .max_fee_per_blob_gas(100)
784 .build()
785 .unwrap();
786
787 assert_eq!(tx.tx_type, TransactionType::Eip4844);
788 assert_eq!(tx.blob_hashes, blob_hashes);
789 assert_eq!(tx.max_fee_per_blob_gas, 100);
790 }
791
792 #[test]
793 fn test_tx_env_builder_build_valid_eip7702() {
794 let auth = RecoveredAuthorization::new_unchecked(
796 Authorization {
797 chain_id: U256::from(1),
798 nonce: 0,
799 address: Address::default(),
800 },
801 RecoveredAuthority::Valid(Address::default()),
802 );
803 let auth_list = vec![Either::Right(auth)];
804
805 let tx = TxEnvBuilder::new()
806 .tx_type(Some(4))
807 .caller(Address::from([1u8; 20]))
808 .gas_limit(50000)
809 .gas_price(30)
810 .gas_priority_fee(Some(10))
811 .kind(TxKind::Call(Address::from([2u8; 20])))
812 .authorization_list(auth_list.clone())
813 .build()
814 .unwrap();
815
816 assert_eq!(tx.tx_type, TransactionType::Eip7702);
817 assert_eq!(tx.authorization_list.len(), 1);
818 }
819
820 #[test]
821 fn test_tx_env_builder_build_create_transaction() {
822 let bytecode = Bytes::from(vec![0x60, 0x80, 0x60, 0x40]);
824 let tx = TxEnvBuilder::new()
825 .kind(TxKind::Create)
826 .data(bytecode.clone())
827 .gas_limit(100000)
828 .gas_price(20)
829 .build()
830 .unwrap();
831
832 assert_eq!(tx.kind, TxKind::Create);
833 assert_eq!(tx.data, bytecode);
834 }
835
836 #[test]
837 fn test_tx_env_builder_build_errors_eip1559_missing_priority_fee() {
838 let result = TxEnvBuilder::new()
840 .tx_type(Some(2))
841 .caller(Address::from([1u8; 20]))
842 .gas_limit(50000)
843 .gas_price(30)
844 .kind(TxKind::Call(Address::from([2u8; 20])))
845 .build();
846
847 assert!(matches!(
848 result,
849 Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559)
850 ));
851 }
852
853 #[test]
854 fn test_tx_env_builder_build_errors_eip4844_missing_blob_hashes() {
855 let result = TxEnvBuilder::new()
857 .tx_type(Some(3))
858 .gas_priority_fee(Some(10))
859 .kind(TxKind::Call(Address::from([2u8; 20])))
860 .build();
861
862 assert!(matches!(
863 result,
864 Err(TxEnvBuildError::MissingBlobHashesForEip4844)
865 ));
866 }
867
868 #[test]
869 fn test_tx_env_builder_build_errors_eip4844_not_call() {
870 let result = TxEnvBuilder::new()
872 .tx_type(Some(3))
873 .gas_priority_fee(Some(10))
874 .blob_hashes(vec![B256::from([5u8; 32])])
875 .kind(TxKind::Create)
876 .build();
877
878 assert!(matches!(
879 result,
880 Err(TxEnvBuildError::MissingTargetForEip4844)
881 ));
882 }
883
884 #[test]
885 fn test_tx_env_builder_build_errors_eip7702_missing_auth_list() {
886 let result = TxEnvBuilder::new()
888 .tx_type(Some(4))
889 .gas_priority_fee(Some(10))
890 .kind(TxKind::Call(Address::from([2u8; 20])))
891 .build();
892
893 assert!(matches!(
894 result,
895 Err(TxEnvBuildError::MissingAuthorizationListForEip7702)
896 ));
897 }
898
899 #[test]
900 fn test_tx_env_builder_build_errors_eip7702_not_call() {
901 let auth = RecoveredAuthorization::new_unchecked(
903 Authorization {
904 chain_id: U256::from(1),
905 nonce: 0,
906 address: Address::default(),
907 },
908 RecoveredAuthority::Valid(Address::default()),
909 );
910 let result = TxEnvBuilder::new()
911 .tx_type(Some(4))
912 .gas_priority_fee(Some(10))
913 .authorization_list(vec![Either::Right(auth)])
914 .kind(TxKind::Create)
915 .build();
916
917 assert!(matches!(result, Err(TxEnvBuildError::DeriveErr(_))));
918 }
919
920 #[test]
921 fn test_tx_env_builder_build_fill_legacy() {
922 let tx = TxEnvBuilder::new()
924 .caller(Address::from([1u8; 20]))
925 .gas_limit(21000)
926 .gas_price(20)
927 .kind(TxKind::Call(Address::from([2u8; 20])))
928 .build_fill();
929
930 assert_eq!(tx.tx_type, TransactionType::Legacy);
931 assert_eq!(tx.gas_priority_fee, None);
932 }
933
934 #[test]
935 fn test_tx_env_builder_build_fill_eip1559_missing_priority_fee() {
936 let tx = TxEnvBuilder::new()
938 .tx_type(Some(2))
939 .caller(Address::from([1u8; 20]))
940 .gas_limit(50000)
941 .gas_price(30)
942 .kind(TxKind::Call(Address::from([2u8; 20])))
943 .build_fill();
944
945 assert_eq!(tx.tx_type, TransactionType::Eip1559);
946 assert_eq!(tx.gas_priority_fee, Some(0));
947 }
948
949 #[test]
950 fn test_tx_env_builder_build_fill_eip4844_missing_blob_hashes() {
951 let tx = TxEnvBuilder::new()
953 .tx_type(Some(3))
954 .gas_priority_fee(Some(10))
955 .kind(TxKind::Call(Address::from([2u8; 20])))
956 .build_fill();
957
958 assert_eq!(tx.tx_type, TransactionType::Eip4844);
959 assert_eq!(tx.blob_hashes.len(), 1);
960 assert_eq!(tx.blob_hashes[0], B256::default());
961 }
962
963 #[test]
964 fn test_tx_env_builder_build_fill_eip4844_create_to_call() {
965 let tx = TxEnvBuilder::new()
967 .tx_type(Some(3))
968 .gas_priority_fee(Some(10))
969 .blob_hashes(vec![B256::from([5u8; 32])])
970 .kind(TxKind::Create)
971 .build_fill();
972
973 assert_eq!(tx.tx_type, TransactionType::Eip4844);
974 assert_eq!(tx.kind, TxKind::Call(Address::default()));
975 }
976
977 #[test]
978 fn test_tx_env_builder_build_fill_eip7702_missing_auth_list() {
979 let tx = TxEnvBuilder::new()
981 .tx_type(Some(4))
982 .gas_priority_fee(Some(10))
983 .kind(TxKind::Call(Address::from([2u8; 20])))
984 .build_fill();
985
986 assert_eq!(tx.tx_type, TransactionType::Eip7702);
987 assert_eq!(tx.authorization_list.len(), 1);
988 }
989
990 #[test]
991 fn test_tx_env_builder_build_fill_eip7702_create_to_call() {
992 let auth = RecoveredAuthorization::new_unchecked(
994 Authorization {
995 chain_id: U256::from(1),
996 nonce: 0,
997 address: Address::default(),
998 },
999 RecoveredAuthority::Valid(Address::default()),
1000 );
1001 let tx = TxEnvBuilder::new()
1002 .tx_type(Some(4))
1003 .gas_priority_fee(Some(10))
1004 .authorization_list(vec![Either::Right(auth)])
1005 .kind(TxKind::Create)
1006 .build_fill();
1007
1008 assert_eq!(tx.tx_type, TransactionType::Eip7702);
1009 assert_eq!(tx.kind, TxKind::Call(Address::default()));
1010 }
1011
1012 #[test]
1013 fn test_tx_env_builder_derive_tx_type_legacy() {
1014 let tx = TxEnvBuilder::new()
1016 .caller(Address::from([1u8; 20]))
1017 .gas_limit(21000)
1018 .gas_price(20)
1019 .build()
1020 .unwrap();
1021
1022 assert_eq!(tx.tx_type, TransactionType::Legacy);
1023 }
1024
1025 #[test]
1026 fn test_tx_env_builder_derive_tx_type_eip2930() {
1027 let access_list = AccessList(vec![AccessListItem {
1029 address: Address::from([3u8; 20]),
1030 storage_keys: vec![B256::from([4u8; 32])],
1031 }]);
1032 let tx = TxEnvBuilder::new()
1033 .caller(Address::from([1u8; 20]))
1034 .access_list(access_list)
1035 .build()
1036 .unwrap();
1037
1038 assert_eq!(tx.tx_type, TransactionType::Eip2930);
1039 }
1040
1041 #[test]
1042 fn test_tx_env_builder_derive_tx_type_eip1559() {
1043 let tx = TxEnvBuilder::new()
1045 .caller(Address::from([1u8; 20]))
1046 .gas_priority_fee(Some(10))
1047 .build()
1048 .unwrap();
1049
1050 assert_eq!(tx.tx_type, TransactionType::Eip1559);
1051 }
1052
1053 #[test]
1054 fn test_tx_env_builder_derive_tx_type_eip4844() {
1055 let tx = TxEnvBuilder::new()
1057 .caller(Address::from([1u8; 20]))
1058 .gas_priority_fee(Some(10))
1059 .blob_hashes(vec![B256::from([5u8; 32])])
1060 .kind(TxKind::Call(Address::from([2u8; 20])))
1061 .build()
1062 .unwrap();
1063
1064 assert_eq!(tx.tx_type, TransactionType::Eip4844);
1065 }
1066
1067 #[test]
1068 fn test_tx_env_builder_derive_tx_type_eip7702() {
1069 let auth = RecoveredAuthorization::new_unchecked(
1071 Authorization {
1072 chain_id: U256::from(1),
1073 nonce: 0,
1074 address: Address::default(),
1075 },
1076 RecoveredAuthority::Valid(Address::default()),
1077 );
1078 let tx = TxEnvBuilder::new()
1079 .caller(Address::from([1u8; 20]))
1080 .gas_priority_fee(Some(10))
1081 .authorization_list(vec![Either::Right(auth)])
1082 .kind(TxKind::Call(Address::from([2u8; 20])))
1083 .build()
1084 .unwrap();
1085
1086 assert_eq!(tx.tx_type, TransactionType::Eip7702);
1087 }
1088
1089 #[test]
1090 fn test_tx_env_builder_custom_tx_type() {
1091 let tx = TxEnvBuilder::new()
1093 .tx_type(Some(0xFF))
1094 .caller(Address::from([1u8; 20]))
1095 .build()
1096 .unwrap();
1097
1098 assert_eq!(tx.tx_type, TransactionType::Custom);
1099 }
1100
1101 #[test]
1102 fn test_tx_env_builder_chain_methods() {
1103 let tx = TxEnvBuilder::new()
1105 .caller(Address::from([1u8; 20]))
1106 .gas_limit(50000)
1107 .gas_price(25)
1108 .kind(TxKind::Call(Address::from([2u8; 20])))
1109 .value(U256::from(1000))
1110 .data(Bytes::from(vec![0x12, 0x34]))
1111 .nonce(10)
1112 .chain_id(Some(5))
1113 .access_list(AccessList(vec![AccessListItem {
1114 address: Address::from([3u8; 20]),
1115 storage_keys: vec![],
1116 }]))
1117 .gas_priority_fee(Some(5))
1118 .blob_hashes(vec![B256::from([7u8; 32])])
1119 .max_fee_per_blob_gas(200)
1120 .build_fill();
1121
1122 assert_eq!(tx.caller, Address::from([1u8; 20]));
1123 assert_eq!(tx.gas_limit, 50000);
1124 assert_eq!(tx.gas_price, 25);
1125 assert_eq!(tx.kind, TxKind::Call(Address::from([2u8; 20])));
1126 assert_eq!(tx.value, U256::from(1000));
1127 assert_eq!(tx.data, Bytes::from(vec![0x12, 0x34]));
1128 assert_eq!(tx.nonce, 10);
1129 assert_eq!(tx.chain_id, Some(5));
1130 assert_eq!(tx.access_list.len(), 1);
1131 assert_eq!(tx.gas_priority_fee, Some(5));
1132 assert_eq!(tx.blob_hashes.len(), 1);
1133 assert_eq!(tx.max_fee_per_blob_gas, 200);
1134 }
1135
1136 #[test]
1137 fn test_effective_gas_price() {
1138 assert_eq!(90, effective_gas_setup(TransactionType::Legacy, 90, None));
1139 assert_eq!(
1140 90,
1141 effective_gas_setup(TransactionType::Legacy, 90, Some(0))
1142 );
1143 assert_eq!(
1144 90,
1145 effective_gas_setup(TransactionType::Legacy, 90, Some(10))
1146 );
1147 assert_eq!(
1148 120,
1149 effective_gas_setup(TransactionType::Legacy, 120, Some(10))
1150 );
1151 assert_eq!(90, effective_gas_setup(TransactionType::Eip2930, 90, None));
1152 assert_eq!(
1153 90,
1154 effective_gas_setup(TransactionType::Eip2930, 90, Some(0))
1155 );
1156 assert_eq!(
1157 90,
1158 effective_gas_setup(TransactionType::Eip2930, 90, Some(10))
1159 );
1160 assert_eq!(
1161 120,
1162 effective_gas_setup(TransactionType::Eip2930, 120, Some(10))
1163 );
1164 assert_eq!(90, effective_gas_setup(TransactionType::Eip1559, 90, None));
1165 assert_eq!(
1166 90,
1167 effective_gas_setup(TransactionType::Eip1559, 90, Some(0))
1168 );
1169 assert_eq!(
1170 90,
1171 effective_gas_setup(TransactionType::Eip1559, 90, Some(10))
1172 );
1173 assert_eq!(
1174 110,
1175 effective_gas_setup(TransactionType::Eip1559, 120, Some(10))
1176 );
1177 assert_eq!(90, effective_gas_setup(TransactionType::Eip4844, 90, None));
1178 assert_eq!(
1179 90,
1180 effective_gas_setup(TransactionType::Eip4844, 90, Some(0))
1181 );
1182 assert_eq!(
1183 90,
1184 effective_gas_setup(TransactionType::Eip4844, 90, Some(10))
1185 );
1186 assert_eq!(
1187 110,
1188 effective_gas_setup(TransactionType::Eip4844, 120, Some(10))
1189 );
1190 assert_eq!(90, effective_gas_setup(TransactionType::Eip7702, 90, None));
1191 assert_eq!(
1192 90,
1193 effective_gas_setup(TransactionType::Eip7702, 90, Some(0))
1194 );
1195 assert_eq!(
1196 90,
1197 effective_gas_setup(TransactionType::Eip7702, 90, Some(10))
1198 );
1199 assert_eq!(
1200 110,
1201 effective_gas_setup(TransactionType::Eip7702, 120, Some(10))
1202 );
1203 }
1204}