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 primitives::{Address, Bytes, TxKind, B256, U256};
12use std::{vec, vec::Vec};
13
14#[derive(Clone, Debug, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct TxEnv {
22    pub tx_type: u8,
24    pub caller: Address,
26    pub gas_limit: u64,
28    pub gas_price: u128,
32    pub kind: TxKind,
34    pub value: U256,
36    pub data: Bytes,
38
39    pub nonce: u64,
41
42    pub chain_id: Option<u64>,
50
51    pub access_list: AccessList,
57
58    pub gas_priority_fee: Option<u128>,
64
65    pub blob_hashes: Vec<B256>,
73
74    pub max_fee_per_blob_gas: u128,
80
81    pub authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
90    }
96
97impl Default for TxEnv {
98    fn default() -> Self {
99        Self::builder().build().unwrap()
100    }
101}
102
103#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub enum DeriveTxTypeError {
107    MissingTargetForEip4844,
109    MissingTargetForEip7702,
111    MissingTargetForEip7873,
113}
114
115impl TxEnv {
116    pub fn derive_tx_type(&mut self) -> Result<(), DeriveTxTypeError> {
119        if !self.access_list.0.is_empty() {
120            self.tx_type = TransactionType::Eip2930 as u8;
121        }
122
123        if self.gas_priority_fee.is_some() {
124            self.tx_type = TransactionType::Eip1559 as u8;
125        }
126
127        if !self.blob_hashes.is_empty() || self.max_fee_per_blob_gas > 0 {
128            if let TxKind::Call(_) = self.kind {
129                self.tx_type = TransactionType::Eip4844 as u8;
130                return Ok(());
131            } else {
132                return Err(DeriveTxTypeError::MissingTargetForEip4844);
133            }
134        }
135
136        if !self.authorization_list.is_empty() {
137            if let TxKind::Call(_) = self.kind {
138                self.tx_type = TransactionType::Eip7702 as u8;
139                return Ok(());
140            } else {
141                return Err(DeriveTxTypeError::MissingTargetForEip7702);
142            }
143        }
144
145        Ok(())
156    }
157
158    pub fn set_signed_authorization(&mut self, auth: Vec<SignedAuthorization>) {
160        self.authorization_list = auth.into_iter().map(Either::Left).collect();
161    }
162
163    pub fn set_recovered_authorization(&mut self, auth: Vec<RecoveredAuthorization>) {
165        self.authorization_list = auth.into_iter().map(Either::Right).collect();
166    }
167}
168
169impl Transaction for TxEnv {
170    type AccessListItem<'a> = &'a AccessListItem;
171    type Authorization<'a> = &'a Either<SignedAuthorization, RecoveredAuthorization>;
172
173    fn tx_type(&self) -> u8 {
174        self.tx_type
175    }
176
177    fn kind(&self) -> TxKind {
178        self.kind
179    }
180
181    fn caller(&self) -> Address {
182        self.caller
183    }
184
185    fn gas_limit(&self) -> u64 {
186        self.gas_limit
187    }
188
189    fn gas_price(&self) -> u128 {
190        self.gas_price
191    }
192
193    fn value(&self) -> U256 {
194        self.value
195    }
196
197    fn nonce(&self) -> u64 {
198        self.nonce
199    }
200
201    fn chain_id(&self) -> Option<u64> {
202        self.chain_id
203    }
204
205    fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
206        Some(self.access_list.0.iter())
207    }
208
209    fn max_fee_per_gas(&self) -> u128 {
210        self.gas_price
211    }
212
213    fn max_fee_per_blob_gas(&self) -> u128 {
214        self.max_fee_per_blob_gas
215    }
216
217    fn authorization_list_len(&self) -> usize {
218        self.authorization_list.len()
219    }
220
221    fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
222        self.authorization_list.iter()
223    }
224
225    fn input(&self) -> &Bytes {
226        &self.data
227    }
228
229    fn blob_versioned_hashes(&self) -> &[B256] {
230        &self.blob_hashes
231    }
232
233    fn max_priority_fee_per_gas(&self) -> Option<u128> {
234        self.gas_priority_fee
235    }
236
237    }
242
243#[derive(Default, Debug)]
245pub struct TxEnvBuilder {
246    tx_type: Option<u8>,
247    caller: Address,
248    gas_limit: u64,
249    gas_price: u128,
250    kind: TxKind,
251    value: U256,
252    data: Bytes,
253    nonce: u64,
254    chain_id: Option<u64>,
255    access_list: AccessList,
256    gas_priority_fee: Option<u128>,
257    blob_hashes: Vec<B256>,
258    max_fee_per_blob_gas: u128,
259    authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
260}
261
262impl TxEnvBuilder {
263    pub fn new() -> Self {
265        Self {
266            tx_type: None,
267            caller: Address::default(),
268            gas_limit: 30_000_000,
269            gas_price: 0,
270            kind: TxKind::Call(Address::default()),
271            value: U256::ZERO,
272            data: Bytes::default(),
273            nonce: 0,
274            chain_id: Some(1), access_list: Default::default(),
276            gas_priority_fee: None,
277            blob_hashes: Vec::new(),
278            max_fee_per_blob_gas: 0,
279            authorization_list: Vec::new(),
280        }
281    }
282
283    pub fn tx_type(mut self, tx_type: Option<u8>) -> Self {
285        self.tx_type = tx_type;
286        self
287    }
288
289    pub fn caller(mut self, caller: Address) -> Self {
291        self.caller = caller;
292        self
293    }
294
295    pub fn gas_limit(mut self, gas_limit: u64) -> Self {
297        self.gas_limit = gas_limit;
298        self
299    }
300
301    pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
303        self.gas_price = max_fee_per_gas;
304        self
305    }
306
307    pub fn gas_price(mut self, gas_price: u128) -> Self {
309        self.gas_price = gas_price;
310        self
311    }
312
313    pub fn kind(mut self, kind: TxKind) -> Self {
315        self.kind = kind;
316        self
317    }
318
319    pub fn value(mut self, value: U256) -> Self {
321        self.value = value;
322        self
323    }
324
325    pub fn data(mut self, data: Bytes) -> Self {
327        self.data = data;
328        self
329    }
330
331    pub fn nonce(mut self, nonce: u64) -> Self {
333        self.nonce = nonce;
334        self
335    }
336
337    pub fn chain_id(mut self, chain_id: Option<u64>) -> Self {
339        self.chain_id = chain_id;
340        self
341    }
342
343    pub fn access_list(mut self, access_list: AccessList) -> Self {
345        self.access_list = access_list;
346        self
347    }
348
349    pub fn gas_priority_fee(mut self, gas_priority_fee: Option<u128>) -> Self {
351        self.gas_priority_fee = gas_priority_fee;
352        self
353    }
354
355    pub fn blob_hashes(mut self, blob_hashes: Vec<B256>) -> Self {
357        self.blob_hashes = blob_hashes;
358        self
359    }
360
361    pub fn max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self {
363        self.max_fee_per_blob_gas = max_fee_per_blob_gas;
364        self
365    }
366
367    pub fn authorization_list(
369        mut self,
370        authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
371    ) -> Self {
372        self.authorization_list = authorization_list;
373        self
374    }
375
376    pub fn build_fill(mut self) -> TxEnv {
378        let tx_type_not_set = self.tx_type.is_some();
379        if let Some(tx_type) = self.tx_type {
380            match TransactionType::from(tx_type) {
381                TransactionType::Legacy => {
382                    }
384                TransactionType::Eip2930 => {
385                    }
387                TransactionType::Eip1559 => {
388                    if self.gas_priority_fee.is_none() {
390                        self.gas_priority_fee = Some(0);
391                    }
392                }
393                TransactionType::Eip4844 => {
394                    if self.gas_priority_fee.is_none() {
396                        self.gas_priority_fee = Some(0);
397                    }
398
399                    if self.blob_hashes.is_empty() {
401                        self.blob_hashes = vec![B256::default()];
402                    }
403
404                    if !self.kind.is_call() {
406                        self.kind = TxKind::Call(Address::default());
407                    }
408                }
409                TransactionType::Eip7702 => {
410                    if self.gas_priority_fee.is_none() {
412                        self.gas_priority_fee = Some(0);
413                    }
414
415                    if self.authorization_list.is_empty() {
417                        self.authorization_list =
419                            vec![Either::Right(RecoveredAuthorization::new_unchecked(
420                                Authorization {
421                                    chain_id: U256::from(self.chain_id.unwrap_or(1)),
422                                    address: self.caller,
423                                    nonce: self.nonce,
424                                },
425                                RecoveredAuthority::Invalid,
426                            ))];
427                    }
428
429                    if !self.kind.is_call() {
431                        self.kind = TxKind::Call(Address::default());
432                    }
433                }
434                TransactionType::Custom => {
435                    }
437            }
438        }
439
440        let mut tx = TxEnv {
441            tx_type: self.tx_type.unwrap_or(0),
442            caller: self.caller,
443            gas_limit: self.gas_limit,
444            gas_price: self.gas_price,
445            kind: self.kind,
446            value: self.value,
447            data: self.data,
448            nonce: self.nonce,
449            chain_id: self.chain_id,
450            access_list: self.access_list,
451            gas_priority_fee: self.gas_priority_fee,
452            blob_hashes: self.blob_hashes,
453            max_fee_per_blob_gas: self.max_fee_per_blob_gas,
454            authorization_list: self.authorization_list,
455        };
456
457        if tx_type_not_set {
459            match tx.derive_tx_type() {
460                Ok(_) => {}
461                Err(DeriveTxTypeError::MissingTargetForEip4844) => {
462                    tx.kind = TxKind::Call(Address::default());
463                }
464                Err(DeriveTxTypeError::MissingTargetForEip7702) => {
465                    tx.kind = TxKind::Call(Address::default());
466                }
467                Err(DeriveTxTypeError::MissingTargetForEip7873) => {
468                    tx.kind = TxKind::Call(Address::default());
469                }
470            }
471        }
472
473        tx
474    }
475
476    pub fn build(self) -> Result<TxEnv, TxEnvBuildError> {
479        if let Some(tx_type) = self.tx_type {
481            match TransactionType::from(tx_type) {
482                TransactionType::Legacy => {
483                    }
485                TransactionType::Eip2930 => {
486                    }
488                TransactionType::Eip1559 => {
489                    if self.gas_priority_fee.is_none() {
491                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
492                    }
493                }
494                TransactionType::Eip4844 => {
495                    if self.gas_priority_fee.is_none() {
497                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
498                    }
499
500                    if self.blob_hashes.is_empty() {
502                        return Err(TxEnvBuildError::MissingBlobHashesForEip4844);
503                    }
504
505                    if !self.kind.is_call() {
507                        return Err(TxEnvBuildError::MissingTargetForEip4844);
508                    }
509                }
510                TransactionType::Eip7702 => {
511                    if self.gas_priority_fee.is_none() {
513                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
514                    }
515
516                    if self.authorization_list.is_empty() {
518                        return Err(TxEnvBuildError::MissingAuthorizationListForEip7702);
519                    }
520
521                    if !self.kind.is_call() {
523                        return Err(DeriveTxTypeError::MissingTargetForEip4844.into());
524                    }
525                }
526                _ => {
527                    panic!()
528                }
529            }
530        }
531
532        let mut tx = TxEnv {
533            tx_type: self.tx_type.unwrap_or(0),
534            caller: self.caller,
535            gas_limit: self.gas_limit,
536            gas_price: self.gas_price,
537            kind: self.kind,
538            value: self.value,
539            data: self.data,
540            nonce: self.nonce,
541            chain_id: self.chain_id,
542            access_list: self.access_list,
543            gas_priority_fee: self.gas_priority_fee,
544            blob_hashes: self.blob_hashes,
545            max_fee_per_blob_gas: self.max_fee_per_blob_gas,
546            authorization_list: self.authorization_list,
547        };
548
549        tx.derive_tx_type()?;
551
552        Ok(tx)
553    }
554}
555
556#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
558#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
559pub enum TxEnvBuildError {
560    DeriveErr(DeriveTxTypeError),
562    MissingGasPriorityFeeForEip1559,
564    MissingBlobHashesForEip4844,
566    MissingAuthorizationListForEip7702,
568    MissingTargetForEip4844,
570}
571
572impl From<DeriveTxTypeError> for TxEnvBuildError {
573    fn from(error: DeriveTxTypeError) -> Self {
574        TxEnvBuildError::DeriveErr(error)
575    }
576}
577
578impl TxEnv {
579    pub fn builder() -> TxEnvBuilder {
581        TxEnvBuilder::new()
582    }
583}
584
585#[cfg(test)]
586mod tests {
587    use super::*;
588
589    fn effective_gas_setup(
590        tx_type: TransactionType,
591        gas_price: u128,
592        gas_priority_fee: Option<u128>,
593    ) -> u128 {
594        let tx = TxEnv {
595            tx_type: tx_type as u8,
596            gas_price,
597            gas_priority_fee,
598            ..Default::default()
599        };
600        let base_fee = 100;
601        tx.effective_gas_price(base_fee)
602    }
603
604    #[test]
605    fn test_effective_gas_price() {
606        assert_eq!(90, effective_gas_setup(TransactionType::Legacy, 90, None));
607        assert_eq!(
608            90,
609            effective_gas_setup(TransactionType::Legacy, 90, Some(0))
610        );
611        assert_eq!(
612            90,
613            effective_gas_setup(TransactionType::Legacy, 90, Some(10))
614        );
615        assert_eq!(
616            120,
617            effective_gas_setup(TransactionType::Legacy, 120, Some(10))
618        );
619        assert_eq!(90, effective_gas_setup(TransactionType::Eip2930, 90, None));
620        assert_eq!(
621            90,
622            effective_gas_setup(TransactionType::Eip2930, 90, Some(0))
623        );
624        assert_eq!(
625            90,
626            effective_gas_setup(TransactionType::Eip2930, 90, Some(10))
627        );
628        assert_eq!(
629            120,
630            effective_gas_setup(TransactionType::Eip2930, 120, Some(10))
631        );
632        assert_eq!(90, effective_gas_setup(TransactionType::Eip1559, 90, None));
633        assert_eq!(
634            90,
635            effective_gas_setup(TransactionType::Eip1559, 90, Some(0))
636        );
637        assert_eq!(
638            90,
639            effective_gas_setup(TransactionType::Eip1559, 90, Some(10))
640        );
641        assert_eq!(
642            110,
643            effective_gas_setup(TransactionType::Eip1559, 120, Some(10))
644        );
645        assert_eq!(90, effective_gas_setup(TransactionType::Eip4844, 90, None));
646        assert_eq!(
647            90,
648            effective_gas_setup(TransactionType::Eip4844, 90, Some(0))
649        );
650        assert_eq!(
651            90,
652            effective_gas_setup(TransactionType::Eip4844, 90, Some(10))
653        );
654        assert_eq!(
655            110,
656            effective_gas_setup(TransactionType::Eip4844, 120, Some(10))
657        );
658        assert_eq!(90, effective_gas_setup(TransactionType::Eip7702, 90, None));
659        assert_eq!(
660            90,
661            effective_gas_setup(TransactionType::Eip7702, 90, Some(0))
662        );
663        assert_eq!(
664            90,
665            effective_gas_setup(TransactionType::Eip7702, 90, Some(10))
666        );
667        assert_eq!(
668            110,
669            effective_gas_setup(TransactionType::Eip7702, 120, Some(10))
670        );
671    }
672}