revm_context/
tx.rs

1//! This module contains [`TxEnv`] struct and implements [`Transaction`] trait for it.
2use 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/// The Transaction Environment is a struct that contains all fields that can be found in all Ethereum transaction,
15/// including EIP-4844, EIP-7702, EIP-7873, etc.  It implements the [`Transaction`] trait, which is used inside the EVM to execute a transaction.
16///
17/// [`TxEnvBuilder`] builder is recommended way to create a new [`TxEnv`] as it will automatically
18/// set the transaction type based on the fields set.
19#[derive(Clone, Debug, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct TxEnv {
22    /// Transaction type
23    pub tx_type: u8,
24    /// Caller aka Author aka transaction signer
25    pub caller: Address,
26    /// The gas limit of the transaction.
27    pub gas_limit: u64,
28    /// The gas price of the transaction.
29    ///
30    /// For EIP-1559 transaction this represent max_gas_fee.
31    pub gas_price: u128,
32    /// The destination of the transaction
33    pub kind: TxKind,
34    /// The value sent to `transact_to`
35    pub value: U256,
36    /// The data of the transaction
37    pub data: Bytes,
38
39    /// The nonce of the transaction
40    pub nonce: u64,
41
42    /// The chain ID of the transaction
43    ///
44    /// If set to [`None`], no checks are performed.
45    ///
46    /// Incorporated as part of the Spurious Dragon upgrade via [EIP-155].
47    ///
48    /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155
49    pub chain_id: Option<u64>,
50
51    /// A list of addresses and storage keys that the transaction plans to access
52    ///
53    /// Added in [EIP-2930].
54    ///
55    /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930
56    pub access_list: AccessList,
57
58    /// The priority fee per gas
59    ///
60    /// Incorporated as part of the London upgrade via [EIP-1559].
61    ///
62    /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559
63    pub gas_priority_fee: Option<u128>,
64
65    /// The list of blob versioned hashes
66    ///
67    /// Per EIP there should be at least one blob present if [`max_fee_per_blob_gas`][Self::max_fee_per_blob_gas] is [`Some`].
68    ///
69    /// Incorporated as part of the Cancun upgrade via [EIP-4844].
70    ///
71    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
72    pub blob_hashes: Vec<B256>,
73
74    /// The max fee per blob gas
75    ///
76    /// Incorporated as part of the Cancun upgrade via [EIP-4844].
77    ///
78    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
79    pub max_fee_per_blob_gas: u128,
80
81    /// List of authorizations
82    ///
83    /// `authorization_list` contains the signature that authorizes this
84    /// caller to place the code to signer account.
85    ///
86    /// Set EOA account code for one transaction via [EIP-7702].
87    ///
88    /// [EIP-7702]: https://eips.ethereum.org/EIPS/eip-7702
89    pub authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
90    // TODO(EOF)
91    // /// List of initcodes that is part of Initcode transaction.
92    // ///
93    // /// [EIP-7873](https://eips.ethereum.org/EIPS/eip-7873)
94    // pub initcodes: Vec<Bytes>,
95}
96
97impl Default for TxEnv {
98    fn default() -> Self {
99        Self::builder().build().unwrap()
100    }
101}
102
103/// Error type for deriving transaction type used as error in [`TxEnv::derive_tx_type`] function.
104#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub enum DeriveTxTypeError {
107    /// Missing target for EIP-4844
108    MissingTargetForEip4844,
109    /// Missing target for EIP-7702
110    MissingTargetForEip7702,
111    /// Missing target for EIP-7873
112    MissingTargetForEip7873,
113}
114
115impl TxEnv {
116    /// Derives tx type from transaction fields and sets it to `tx_type`.
117    /// Returns error in case some fields were not set correctly.
118    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        // TODO(EOF)
146        // if !self.initcodes.is_empty() {
147        //     if let TxKind::Call(_) = self.kind {
148        //         self.tx_type = TransactionType::Eip7873 as u8;
149        //         return Ok(());
150        //     } else {
151        //         return Err(DeriveTxTypeError::MissingTargetForEip7873);
152        //     }
153        // }
154
155        Ok(())
156    }
157
158    /// Insert a list of signed authorizations into the authorization list.
159    pub fn set_signed_authorization(&mut self, auth: Vec<SignedAuthorization>) {
160        self.authorization_list = auth.into_iter().map(Either::Left).collect();
161    }
162
163    /// Insert a list of recovered authorizations into the authorization list.
164    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    // TODO(EOF)
238    // fn initcodes(&self) -> &[Bytes] {
239    //     &self.initcodes
240    // }
241}
242
243/// Builder for constructing [`TxEnv`] instances
244#[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    /// Create a new builder with default values
264    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), // Mainnet chain ID is 1
275            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    /// Set the transaction type
284    pub fn tx_type(mut self, tx_type: Option<u8>) -> Self {
285        self.tx_type = tx_type;
286        self
287    }
288
289    /// Set the caller address
290    pub fn caller(mut self, caller: Address) -> Self {
291        self.caller = caller;
292        self
293    }
294
295    /// Set the gas limit
296    pub fn gas_limit(mut self, gas_limit: u64) -> Self {
297        self.gas_limit = gas_limit;
298        self
299    }
300
301    /// Set the max fee per gas.
302    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    /// Set the gas price
308    pub fn gas_price(mut self, gas_price: u128) -> Self {
309        self.gas_price = gas_price;
310        self
311    }
312
313    /// Set the transaction kind
314    pub fn kind(mut self, kind: TxKind) -> Self {
315        self.kind = kind;
316        self
317    }
318
319    /// Set the transaction value
320    pub fn value(mut self, value: U256) -> Self {
321        self.value = value;
322        self
323    }
324
325    /// Set the transaction data
326    pub fn data(mut self, data: Bytes) -> Self {
327        self.data = data;
328        self
329    }
330
331    /// Set the transaction nonce
332    pub fn nonce(mut self, nonce: u64) -> Self {
333        self.nonce = nonce;
334        self
335    }
336
337    /// Set the chain ID
338    pub fn chain_id(mut self, chain_id: Option<u64>) -> Self {
339        self.chain_id = chain_id;
340        self
341    }
342
343    /// Set the access list
344    pub fn access_list(mut self, access_list: AccessList) -> Self {
345        self.access_list = access_list;
346        self
347    }
348
349    /// Set the gas priority fee
350    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    /// Set the blob hashes
356    pub fn blob_hashes(mut self, blob_hashes: Vec<B256>) -> Self {
357        self.blob_hashes = blob_hashes;
358        self
359    }
360
361    /// Set the max fee per blob gas
362    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    /// Set the authorization list
368    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    /// Build the final [`TxEnv`] with default values for missing fields.
377    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                    // do nothing
383                }
384                TransactionType::Eip2930 => {
385                    // do nothing, all fields are set. Access list can be empty.
386                }
387                TransactionType::Eip1559 => {
388                    // gas priority fee is required
389                    if self.gas_priority_fee.is_none() {
390                        self.gas_priority_fee = Some(0);
391                    }
392                }
393                TransactionType::Eip4844 => {
394                    // gas priority fee is required
395                    if self.gas_priority_fee.is_none() {
396                        self.gas_priority_fee = Some(0);
397                    }
398
399                    // blob hashes can be empty
400                    if self.blob_hashes.is_empty() {
401                        self.blob_hashes = vec![B256::default()];
402                    }
403
404                    // target is required
405                    if !self.kind.is_call() {
406                        self.kind = TxKind::Call(Address::default());
407                    }
408                }
409                TransactionType::Eip7702 => {
410                    // gas priority fee is required
411                    if self.gas_priority_fee.is_none() {
412                        self.gas_priority_fee = Some(0);
413                    }
414
415                    // authorization list can be empty
416                    if self.authorization_list.is_empty() {
417                        // add dummy authorization
418                        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                    // target is required
430                    if !self.kind.is_call() {
431                        self.kind = TxKind::Call(Address::default());
432                    }
433                }
434                TransactionType::Custom => {
435                    // do nothing
436                }
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 is not set, derive it from fields and fix errors.
458        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    /// Build the final [`TxEnv`], returns error if some fields are wrongly set.
477    /// If it is fine to fill missing fields with default values, use [`TxEnvBuilder::build_fill`] instead.
478    pub fn build(self) -> Result<TxEnv, TxEnvBuildError> {
479        // if tx_type is set, check if all needed fields are set correctly.
480        if let Some(tx_type) = self.tx_type {
481            match TransactionType::from(tx_type) {
482                TransactionType::Legacy => {
483                    // do nothing
484                }
485                TransactionType::Eip2930 => {
486                    // do nothing, all fields are set. Access list can be empty.
487                }
488                TransactionType::Eip1559 => {
489                    // gas priority fee is required
490                    if self.gas_priority_fee.is_none() {
491                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
492                    }
493                }
494                TransactionType::Eip4844 => {
495                    // gas priority fee is required
496                    if self.gas_priority_fee.is_none() {
497                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
498                    }
499
500                    // blob hashes can be empty
501                    if self.blob_hashes.is_empty() {
502                        return Err(TxEnvBuildError::MissingBlobHashesForEip4844);
503                    }
504
505                    // target is required
506                    if !self.kind.is_call() {
507                        return Err(TxEnvBuildError::MissingTargetForEip4844);
508                    }
509                }
510                TransactionType::Eip7702 => {
511                    // gas priority fee is required
512                    if self.gas_priority_fee.is_none() {
513                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
514                    }
515
516                    // authorization list can be empty
517                    if self.authorization_list.is_empty() {
518                        return Err(TxEnvBuildError::MissingAuthorizationListForEip7702);
519                    }
520
521                    // target is required
522                    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        // Derive tx type from fields, if some fields are wrongly set it will return an error.
550        tx.derive_tx_type()?;
551
552        Ok(tx)
553    }
554}
555
556/// Error type for building [`TxEnv`]
557#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
558#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
559pub enum TxEnvBuildError {
560    /// Derive tx type error
561    DeriveErr(DeriveTxTypeError),
562    /// Missing priority fee for EIP-1559
563    MissingGasPriorityFeeForEip1559,
564    /// Missing blob hashes for EIP-4844
565    MissingBlobHashesForEip4844,
566    /// Missing authorization list for EIP-7702
567    MissingAuthorizationListForEip7702,
568    /// Missing target for EIP-4844
569    MissingTargetForEip4844,
570}
571
572impl From<DeriveTxTypeError> for TxEnvBuildError {
573    fn from(error: DeriveTxTypeError) -> Self {
574        TxEnvBuildError::DeriveErr(error)
575    }
576}
577
578impl TxEnv {
579    /// Create a new builder for constructing a [`TxEnv`]
580    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}