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 }
95
96impl Default for TxEnv {
97 fn default() -> Self {
98 Self::builder().build().unwrap()
99 }
100}
101
102#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
105pub enum DeriveTxTypeError {
106 MissingTargetForEip4844,
108 MissingTargetForEip7702,
110 MissingTargetForEip7873,
112}
113
114impl TxEnv {
115 pub fn new_bench() -> Self {
117 Self {
118 caller: BENCH_CALLER,
119 kind: TxKind::Call(BENCH_TARGET),
120 gas_limit: 1_000_000_000,
121 ..Default::default()
122 }
123 }
124
125 pub fn derive_tx_type(&mut self) -> Result<(), DeriveTxTypeError> {
128 if !self.access_list.0.is_empty() {
129 self.tx_type = TransactionType::Eip2930 as u8;
130 }
131
132 if self.gas_priority_fee.is_some() {
133 self.tx_type = TransactionType::Eip1559 as u8;
134 }
135
136 if !self.blob_hashes.is_empty() || self.max_fee_per_blob_gas > 0 {
137 if let TxKind::Call(_) = self.kind {
138 self.tx_type = TransactionType::Eip4844 as u8;
139 return Ok(());
140 } else {
141 return Err(DeriveTxTypeError::MissingTargetForEip4844);
142 }
143 }
144
145 if !self.authorization_list.is_empty() {
146 if let TxKind::Call(_) = self.kind {
147 self.tx_type = TransactionType::Eip7702 as u8;
148 return Ok(());
149 } else {
150 return Err(DeriveTxTypeError::MissingTargetForEip7702);
151 }
152 }
153
154 Ok(())
165 }
166
167 pub fn set_signed_authorization(&mut self, auth: Vec<SignedAuthorization>) {
169 self.authorization_list = auth.into_iter().map(Either::Left).collect();
170 }
171
172 pub fn set_recovered_authorization(&mut self, auth: Vec<RecoveredAuthorization>) {
174 self.authorization_list = auth.into_iter().map(Either::Right).collect();
175 }
176}
177
178impl Transaction for TxEnv {
179 type AccessListItem<'a> = &'a AccessListItem;
180 type Authorization<'a> = &'a Either<SignedAuthorization, RecoveredAuthorization>;
181
182 fn tx_type(&self) -> u8 {
183 self.tx_type
184 }
185
186 fn kind(&self) -> TxKind {
187 self.kind
188 }
189
190 fn caller(&self) -> Address {
191 self.caller
192 }
193
194 fn gas_limit(&self) -> u64 {
195 self.gas_limit
196 }
197
198 fn gas_price(&self) -> u128 {
199 self.gas_price
200 }
201
202 fn value(&self) -> U256 {
203 self.value
204 }
205
206 fn nonce(&self) -> u64 {
207 self.nonce
208 }
209
210 fn chain_id(&self) -> Option<u64> {
211 self.chain_id
212 }
213
214 fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
215 Some(self.access_list.0.iter())
216 }
217
218 fn max_fee_per_gas(&self) -> u128 {
219 self.gas_price
220 }
221
222 fn max_fee_per_blob_gas(&self) -> u128 {
223 self.max_fee_per_blob_gas
224 }
225
226 fn authorization_list_len(&self) -> usize {
227 self.authorization_list.len()
228 }
229
230 fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
231 self.authorization_list.iter()
232 }
233
234 fn input(&self) -> &Bytes {
235 &self.data
236 }
237
238 fn blob_versioned_hashes(&self) -> &[B256] {
239 &self.blob_hashes
240 }
241
242 fn max_priority_fee_per_gas(&self) -> Option<u128> {
243 self.gas_priority_fee
244 }
245
246 }
251
252#[derive(Default, Debug)]
254pub struct TxEnvBuilder {
255 tx_type: Option<u8>,
256 caller: Address,
257 gas_limit: u64,
258 gas_price: u128,
259 kind: TxKind,
260 value: U256,
261 data: Bytes,
262 nonce: u64,
263 chain_id: Option<u64>,
264 access_list: AccessList,
265 gas_priority_fee: Option<u128>,
266 blob_hashes: Vec<B256>,
267 max_fee_per_blob_gas: u128,
268 authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
269}
270
271impl TxEnvBuilder {
272 pub fn new() -> Self {
274 Self {
275 tx_type: None,
276 caller: Address::default(),
277 gas_limit: eip7825::TX_GAS_LIMIT_CAP,
278 gas_price: 0,
279 kind: TxKind::Call(Address::default()),
280 value: U256::ZERO,
281 data: Bytes::default(),
282 nonce: 0,
283 chain_id: Some(1), access_list: Default::default(),
285 gas_priority_fee: None,
286 blob_hashes: Vec::new(),
287 max_fee_per_blob_gas: 0,
288 authorization_list: Vec::new(),
289 }
290 }
291
292 pub fn tx_type(mut self, tx_type: Option<u8>) -> Self {
294 self.tx_type = tx_type;
295 self
296 }
297
298 pub fn caller(mut self, caller: Address) -> Self {
300 self.caller = caller;
301 self
302 }
303
304 pub fn gas_limit(mut self, gas_limit: u64) -> Self {
306 self.gas_limit = gas_limit;
307 self
308 }
309
310 pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
312 self.gas_price = max_fee_per_gas;
313 self
314 }
315
316 pub fn gas_price(mut self, gas_price: u128) -> Self {
318 self.gas_price = gas_price;
319 self
320 }
321
322 pub fn kind(mut self, kind: TxKind) -> Self {
324 self.kind = kind;
325 self
326 }
327
328 pub fn value(mut self, value: U256) -> Self {
330 self.value = value;
331 self
332 }
333
334 pub fn data(mut self, data: Bytes) -> Self {
336 self.data = data;
337 self
338 }
339
340 pub fn nonce(mut self, nonce: u64) -> Self {
342 self.nonce = nonce;
343 self
344 }
345
346 pub fn chain_id(mut self, chain_id: Option<u64>) -> Self {
348 self.chain_id = chain_id;
349 self
350 }
351
352 pub fn access_list(mut self, access_list: AccessList) -> Self {
354 self.access_list = access_list;
355 self
356 }
357
358 pub fn gas_priority_fee(mut self, gas_priority_fee: Option<u128>) -> Self {
360 self.gas_priority_fee = gas_priority_fee;
361 self
362 }
363
364 pub fn blob_hashes(mut self, blob_hashes: Vec<B256>) -> Self {
366 self.blob_hashes = blob_hashes;
367 self
368 }
369
370 pub fn max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self {
372 self.max_fee_per_blob_gas = max_fee_per_blob_gas;
373 self
374 }
375
376 pub fn authorization_list(
378 mut self,
379 authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
380 ) -> Self {
381 self.authorization_list = authorization_list;
382 self
383 }
384
385 pub fn build_fill(mut self) -> TxEnv {
387 let tx_type_not_set = self.tx_type.is_some();
388 if let Some(tx_type) = self.tx_type {
389 match TransactionType::from(tx_type) {
390 TransactionType::Legacy => {
391 }
393 TransactionType::Eip2930 => {
394 }
396 TransactionType::Eip1559 => {
397 if self.gas_priority_fee.is_none() {
399 self.gas_priority_fee = Some(0);
400 }
401 }
402 TransactionType::Eip4844 => {
403 if self.gas_priority_fee.is_none() {
405 self.gas_priority_fee = Some(0);
406 }
407
408 if self.blob_hashes.is_empty() {
410 self.blob_hashes = vec![B256::default()];
411 }
412
413 if !self.kind.is_call() {
415 self.kind = TxKind::Call(Address::default());
416 }
417 }
418 TransactionType::Eip7702 => {
419 if self.gas_priority_fee.is_none() {
421 self.gas_priority_fee = Some(0);
422 }
423
424 if self.authorization_list.is_empty() {
426 self.authorization_list =
428 vec![Either::Right(RecoveredAuthorization::new_unchecked(
429 Authorization {
430 chain_id: U256::from(self.chain_id.unwrap_or(1)),
431 address: self.caller,
432 nonce: self.nonce,
433 },
434 RecoveredAuthority::Invalid,
435 ))];
436 }
437
438 if !self.kind.is_call() {
440 self.kind = TxKind::Call(Address::default());
441 }
442 }
443 TransactionType::Custom => {
444 }
446 }
447 }
448
449 let mut tx = TxEnv {
450 tx_type: self.tx_type.unwrap_or(0),
451 caller: self.caller,
452 gas_limit: self.gas_limit,
453 gas_price: self.gas_price,
454 kind: self.kind,
455 value: self.value,
456 data: self.data,
457 nonce: self.nonce,
458 chain_id: self.chain_id,
459 access_list: self.access_list,
460 gas_priority_fee: self.gas_priority_fee,
461 blob_hashes: self.blob_hashes,
462 max_fee_per_blob_gas: self.max_fee_per_blob_gas,
463 authorization_list: self.authorization_list,
464 };
465
466 if tx_type_not_set {
468 match tx.derive_tx_type() {
469 Ok(_) => {}
470 Err(DeriveTxTypeError::MissingTargetForEip4844) => {
471 tx.kind = TxKind::Call(Address::default());
472 }
473 Err(DeriveTxTypeError::MissingTargetForEip7702) => {
474 tx.kind = TxKind::Call(Address::default());
475 }
476 Err(DeriveTxTypeError::MissingTargetForEip7873) => {
477 tx.kind = TxKind::Call(Address::default());
478 }
479 }
480 }
481
482 tx
483 }
484
485 pub fn build(self) -> Result<TxEnv, TxEnvBuildError> {
488 if let Some(tx_type) = self.tx_type {
490 match TransactionType::from(tx_type) {
491 TransactionType::Legacy => {
492 }
494 TransactionType::Eip2930 => {
495 }
497 TransactionType::Eip1559 => {
498 if self.gas_priority_fee.is_none() {
500 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
501 }
502 }
503 TransactionType::Eip4844 => {
504 if self.gas_priority_fee.is_none() {
506 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
507 }
508
509 if self.blob_hashes.is_empty() {
511 return Err(TxEnvBuildError::MissingBlobHashesForEip4844);
512 }
513
514 if !self.kind.is_call() {
516 return Err(TxEnvBuildError::MissingTargetForEip4844);
517 }
518 }
519 TransactionType::Eip7702 => {
520 if self.gas_priority_fee.is_none() {
522 return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
523 }
524
525 if self.authorization_list.is_empty() {
527 return Err(TxEnvBuildError::MissingAuthorizationListForEip7702);
528 }
529
530 if !self.kind.is_call() {
532 return Err(DeriveTxTypeError::MissingTargetForEip4844.into());
533 }
534 }
535 _ => {
536 panic!()
537 }
538 }
539 }
540
541 let mut tx = TxEnv {
542 tx_type: self.tx_type.unwrap_or(0),
543 caller: self.caller,
544 gas_limit: self.gas_limit,
545 gas_price: self.gas_price,
546 kind: self.kind,
547 value: self.value,
548 data: self.data,
549 nonce: self.nonce,
550 chain_id: self.chain_id,
551 access_list: self.access_list,
552 gas_priority_fee: self.gas_priority_fee,
553 blob_hashes: self.blob_hashes,
554 max_fee_per_blob_gas: self.max_fee_per_blob_gas,
555 authorization_list: self.authorization_list,
556 };
557
558 tx.derive_tx_type()?;
560
561 Ok(tx)
562 }
563}
564
565#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
567#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
568pub enum TxEnvBuildError {
569 DeriveErr(DeriveTxTypeError),
571 MissingGasPriorityFeeForEip1559,
573 MissingBlobHashesForEip4844,
575 MissingAuthorizationListForEip7702,
577 MissingTargetForEip4844,
579}
580
581impl From<DeriveTxTypeError> for TxEnvBuildError {
582 fn from(error: DeriveTxTypeError) -> Self {
583 TxEnvBuildError::DeriveErr(error)
584 }
585}
586
587impl TxEnv {
588 pub fn builder() -> TxEnvBuilder {
590 TxEnvBuilder::new()
591 }
592
593 pub fn builder_for_bench() -> TxEnvBuilder {
595 TxEnv::new_bench().modify()
596 }
597
598 pub fn modify(self) -> TxEnvBuilder {
600 let TxEnv {
601 tx_type,
602 caller,
603 gas_limit,
604 gas_price,
605 kind,
606 value,
607 data,
608 nonce,
609 chain_id,
610 access_list,
611 gas_priority_fee,
612 blob_hashes,
613 max_fee_per_blob_gas,
614 authorization_list,
615 } = self;
616
617 TxEnvBuilder::new()
618 .tx_type(Some(tx_type))
619 .caller(caller)
620 .gas_limit(gas_limit)
621 .gas_price(gas_price)
622 .kind(kind)
623 .value(value)
624 .data(data)
625 .nonce(nonce)
626 .chain_id(chain_id)
627 .access_list(access_list)
628 .gas_priority_fee(gas_priority_fee)
629 .blob_hashes(blob_hashes)
630 .max_fee_per_blob_gas(max_fee_per_blob_gas)
631 .authorization_list(authorization_list)
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638
639 fn effective_gas_setup(
640 tx_type: TransactionType,
641 gas_price: u128,
642 gas_priority_fee: Option<u128>,
643 ) -> u128 {
644 let tx = TxEnv {
645 tx_type: tx_type as u8,
646 gas_price,
647 gas_priority_fee,
648 ..Default::default()
649 };
650 let base_fee = 100;
651 tx.effective_gas_price(base_fee)
652 }
653
654 #[test]
655 fn test_effective_gas_price() {
656 assert_eq!(90, effective_gas_setup(TransactionType::Legacy, 90, None));
657 assert_eq!(
658 90,
659 effective_gas_setup(TransactionType::Legacy, 90, Some(0))
660 );
661 assert_eq!(
662 90,
663 effective_gas_setup(TransactionType::Legacy, 90, Some(10))
664 );
665 assert_eq!(
666 120,
667 effective_gas_setup(TransactionType::Legacy, 120, Some(10))
668 );
669 assert_eq!(90, effective_gas_setup(TransactionType::Eip2930, 90, None));
670 assert_eq!(
671 90,
672 effective_gas_setup(TransactionType::Eip2930, 90, Some(0))
673 );
674 assert_eq!(
675 90,
676 effective_gas_setup(TransactionType::Eip2930, 90, Some(10))
677 );
678 assert_eq!(
679 120,
680 effective_gas_setup(TransactionType::Eip2930, 120, Some(10))
681 );
682 assert_eq!(90, effective_gas_setup(TransactionType::Eip1559, 90, None));
683 assert_eq!(
684 90,
685 effective_gas_setup(TransactionType::Eip1559, 90, Some(0))
686 );
687 assert_eq!(
688 90,
689 effective_gas_setup(TransactionType::Eip1559, 90, Some(10))
690 );
691 assert_eq!(
692 110,
693 effective_gas_setup(TransactionType::Eip1559, 120, Some(10))
694 );
695 assert_eq!(90, effective_gas_setup(TransactionType::Eip4844, 90, None));
696 assert_eq!(
697 90,
698 effective_gas_setup(TransactionType::Eip4844, 90, Some(0))
699 );
700 assert_eq!(
701 90,
702 effective_gas_setup(TransactionType::Eip4844, 90, Some(10))
703 );
704 assert_eq!(
705 110,
706 effective_gas_setup(TransactionType::Eip4844, 120, Some(10))
707 );
708 assert_eq!(90, effective_gas_setup(TransactionType::Eip7702, 90, None));
709 assert_eq!(
710 90,
711 effective_gas_setup(TransactionType::Eip7702, 90, Some(0))
712 );
713 assert_eq!(
714 90,
715 effective_gas_setup(TransactionType::Eip7702, 90, Some(10))
716 );
717 assert_eq!(
718 110,
719 effective_gas_setup(TransactionType::Eip7702, 120, Some(10))
720 );
721 }
722}