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 database_interface::{BENCH_CALLER, BENCH_TARGET};
12use primitives::{Address, Bytes, TxKind, B256, U256};
13use std::{vec, vec::Vec};
14
15/// The Transaction Environment is a struct that contains all fields that can be found in all Ethereum transaction,
16/// including EIP-4844, EIP-7702, EIP-7873, etc.  It implements the [`Transaction`] trait, which is used inside the EVM to execute a transaction.
17///
18/// [`TxEnvBuilder`] builder is recommended way to create a new [`TxEnv`] as it will automatically
19/// set the transaction type based on the fields set.
20#[derive(Clone, Debug, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct TxEnv {
23    /// Transaction type
24    pub tx_type: u8,
25    /// Caller aka Author aka transaction signer
26    pub caller: Address,
27    /// The gas limit of the transaction.
28    pub gas_limit: u64,
29    /// The gas price of the transaction.
30    ///
31    /// For EIP-1559 transaction this represent max_gas_fee.
32    pub gas_price: u128,
33    /// The destination of the transaction
34    pub kind: TxKind,
35    /// The value sent to `transact_to`
36    pub value: U256,
37    /// The data of the transaction
38    pub data: Bytes,
39
40    /// The nonce of the transaction
41    pub nonce: u64,
42
43    /// The chain ID of the transaction
44    ///
45    /// Incorporated as part of the Spurious Dragon upgrade via [EIP-155].
46    ///
47    /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155
48    pub chain_id: Option<u64>,
49
50    /// A list of addresses and storage keys that the transaction plans to access
51    ///
52    /// Added in [EIP-2930].
53    ///
54    /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930
55    pub access_list: AccessList,
56
57    /// The priority fee per gas
58    ///
59    /// Incorporated as part of the London upgrade via [EIP-1559].
60    ///
61    /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559
62    pub gas_priority_fee: Option<u128>,
63
64    /// The list of blob versioned hashes
65    ///
66    /// Per EIP there should be at least one blob present if [`max_fee_per_blob_gas`][Self::max_fee_per_blob_gas] is [`Some`].
67    ///
68    /// Incorporated as part of the Cancun upgrade via [EIP-4844].
69    ///
70    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
71    pub blob_hashes: Vec<B256>,
72
73    /// The max fee per blob gas
74    ///
75    /// Incorporated as part of the Cancun upgrade via [EIP-4844].
76    ///
77    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
78    pub max_fee_per_blob_gas: u128,
79
80    /// List of authorizations
81    ///
82    /// `authorization_list` contains the signature that authorizes this
83    /// caller to place the code to signer account.
84    ///
85    /// Set EOA account code for one transaction via [EIP-7702].
86    ///
87    /// [EIP-7702]: https://eips.ethereum.org/EIPS/eip-7702
88    pub authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
89    // TODO(EOF)
90    // /// List of initcodes that is part of Initcode transaction.
91    // ///
92    // /// [EIP-7873](https://eips.ethereum.org/EIPS/eip-7873)
93    // pub initcodes: Vec<Bytes>,
94}
95
96impl Default for TxEnv {
97    fn default() -> Self {
98        Self::builder().build().unwrap()
99    }
100}
101
102/// Error type for deriving transaction type used as error in [`TxEnv::derive_tx_type`] function.
103#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
105pub enum DeriveTxTypeError {
106    /// Missing target for EIP-4844
107    MissingTargetForEip4844,
108    /// Missing target for EIP-7702
109    MissingTargetForEip7702,
110    /// Missing target for EIP-7873
111    MissingTargetForEip7873,
112}
113
114impl TxEnv {
115    /// Creates a new TxEnv with benchmark-specific values.
116    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    /// Derives tx type from transaction fields and sets it to `tx_type`.
126    /// Returns error in case some fields were not set correctly.
127    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        // TODO(EOF)
155        // if !self.initcodes.is_empty() {
156        //     if let TxKind::Call(_) = self.kind {
157        //         self.tx_type = TransactionType::Eip7873 as u8;
158        //         return Ok(());
159        //     } else {
160        //         return Err(DeriveTxTypeError::MissingTargetForEip7873);
161        //     }
162        // }
163
164        Ok(())
165    }
166
167    /// Insert a list of signed authorizations into the authorization list.
168    pub fn set_signed_authorization(&mut self, auth: Vec<SignedAuthorization>) {
169        self.authorization_list = auth.into_iter().map(Either::Left).collect();
170    }
171
172    /// Insert a list of recovered authorizations into the authorization list.
173    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    // TODO(EOF)
247    // fn initcodes(&self) -> &[Bytes] {
248    //     &self.initcodes
249    // }
250}
251
252/// Builder for constructing [`TxEnv`] instances
253#[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    /// Create a new builder with default values
273    pub fn new() -> Self {
274        Self {
275            tx_type: None,
276            caller: Address::default(),
277            gas_limit: 30_000_000,
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), // Mainnet chain ID is 1
284            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    /// Set the transaction type
293    pub fn tx_type(mut self, tx_type: Option<u8>) -> Self {
294        self.tx_type = tx_type;
295        self
296    }
297
298    /// Set the caller address
299    pub fn caller(mut self, caller: Address) -> Self {
300        self.caller = caller;
301        self
302    }
303
304    /// Set the gas limit
305    pub fn gas_limit(mut self, gas_limit: u64) -> Self {
306        self.gas_limit = gas_limit;
307        self
308    }
309
310    /// Set the max fee per gas.
311    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    /// Set the gas price
317    pub fn gas_price(mut self, gas_price: u128) -> Self {
318        self.gas_price = gas_price;
319        self
320    }
321
322    /// Set the transaction kind
323    pub fn kind(mut self, kind: TxKind) -> Self {
324        self.kind = kind;
325        self
326    }
327
328    /// Set the transaction value
329    pub fn value(mut self, value: U256) -> Self {
330        self.value = value;
331        self
332    }
333
334    /// Set the transaction data
335    pub fn data(mut self, data: Bytes) -> Self {
336        self.data = data;
337        self
338    }
339
340    /// Set the transaction nonce
341    pub fn nonce(mut self, nonce: u64) -> Self {
342        self.nonce = nonce;
343        self
344    }
345
346    /// Set the chain ID
347    pub fn chain_id(mut self, chain_id: Option<u64>) -> Self {
348        self.chain_id = chain_id;
349        self
350    }
351
352    /// Set the access list
353    pub fn access_list(mut self, access_list: AccessList) -> Self {
354        self.access_list = access_list;
355        self
356    }
357
358    /// Set the gas priority fee
359    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    /// Set the blob hashes
365    pub fn blob_hashes(mut self, blob_hashes: Vec<B256>) -> Self {
366        self.blob_hashes = blob_hashes;
367        self
368    }
369
370    /// Set the max fee per blob gas
371    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    /// Set the authorization list
377    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    /// Build the final [`TxEnv`] with default values for missing fields.
386    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                    // do nothing
392                }
393                TransactionType::Eip2930 => {
394                    // do nothing, all fields are set. Access list can be empty.
395                }
396                TransactionType::Eip1559 => {
397                    // gas priority fee is required
398                    if self.gas_priority_fee.is_none() {
399                        self.gas_priority_fee = Some(0);
400                    }
401                }
402                TransactionType::Eip4844 => {
403                    // gas priority fee is required
404                    if self.gas_priority_fee.is_none() {
405                        self.gas_priority_fee = Some(0);
406                    }
407
408                    // blob hashes can be empty
409                    if self.blob_hashes.is_empty() {
410                        self.blob_hashes = vec![B256::default()];
411                    }
412
413                    // target is required
414                    if !self.kind.is_call() {
415                        self.kind = TxKind::Call(Address::default());
416                    }
417                }
418                TransactionType::Eip7702 => {
419                    // gas priority fee is required
420                    if self.gas_priority_fee.is_none() {
421                        self.gas_priority_fee = Some(0);
422                    }
423
424                    // authorization list can be empty
425                    if self.authorization_list.is_empty() {
426                        // add dummy authorization
427                        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                    // target is required
439                    if !self.kind.is_call() {
440                        self.kind = TxKind::Call(Address::default());
441                    }
442                }
443                TransactionType::Custom => {
444                    // do nothing
445                }
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 is not set, derive it from fields and fix errors.
467        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    /// Build the final [`TxEnv`], returns error if some fields are wrongly set.
486    /// If it is fine to fill missing fields with default values, use [`TxEnvBuilder::build_fill`] instead.
487    pub fn build(self) -> Result<TxEnv, TxEnvBuildError> {
488        // if tx_type is set, check if all needed fields are set correctly.
489        if let Some(tx_type) = self.tx_type {
490            match TransactionType::from(tx_type) {
491                TransactionType::Legacy => {
492                    // do nothing
493                }
494                TransactionType::Eip2930 => {
495                    // do nothing, all fields are set. Access list can be empty.
496                }
497                TransactionType::Eip1559 => {
498                    // gas priority fee is required
499                    if self.gas_priority_fee.is_none() {
500                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
501                    }
502                }
503                TransactionType::Eip4844 => {
504                    // gas priority fee is required
505                    if self.gas_priority_fee.is_none() {
506                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
507                    }
508
509                    // blob hashes can be empty
510                    if self.blob_hashes.is_empty() {
511                        return Err(TxEnvBuildError::MissingBlobHashesForEip4844);
512                    }
513
514                    // target is required
515                    if !self.kind.is_call() {
516                        return Err(TxEnvBuildError::MissingTargetForEip4844);
517                    }
518                }
519                TransactionType::Eip7702 => {
520                    // gas priority fee is required
521                    if self.gas_priority_fee.is_none() {
522                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
523                    }
524
525                    // authorization list can be empty
526                    if self.authorization_list.is_empty() {
527                        return Err(TxEnvBuildError::MissingAuthorizationListForEip7702);
528                    }
529
530                    // target is required
531                    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        // Derive tx type from fields, if some fields are wrongly set it will return an error.
559        tx.derive_tx_type()?;
560
561        Ok(tx)
562    }
563}
564
565/// Error type for building [`TxEnv`]
566#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
567#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
568pub enum TxEnvBuildError {
569    /// Derive tx type error
570    DeriveErr(DeriveTxTypeError),
571    /// Missing priority fee for EIP-1559
572    MissingGasPriorityFeeForEip1559,
573    /// Missing blob hashes for EIP-4844
574    MissingBlobHashesForEip4844,
575    /// Missing authorization list for EIP-7702
576    MissingAuthorizationListForEip7702,
577    /// Missing target for EIP-4844
578    MissingTargetForEip4844,
579}
580
581impl From<DeriveTxTypeError> for TxEnvBuildError {
582    fn from(error: DeriveTxTypeError) -> Self {
583        TxEnvBuildError::DeriveErr(error)
584    }
585}
586
587impl TxEnv {
588    /// Create a new builder for constructing a [`TxEnv`]
589    pub fn builder() -> TxEnvBuilder {
590        TxEnvBuilder::new()
591    }
592}
593
594#[cfg(test)]
595mod tests {
596    use super::*;
597
598    fn effective_gas_setup(
599        tx_type: TransactionType,
600        gas_price: u128,
601        gas_priority_fee: Option<u128>,
602    ) -> u128 {
603        let tx = TxEnv {
604            tx_type: tx_type as u8,
605            gas_price,
606            gas_priority_fee,
607            ..Default::default()
608        };
609        let base_fee = 100;
610        tx.effective_gas_price(base_fee)
611    }
612
613    #[test]
614    fn test_effective_gas_price() {
615        assert_eq!(90, effective_gas_setup(TransactionType::Legacy, 90, None));
616        assert_eq!(
617            90,
618            effective_gas_setup(TransactionType::Legacy, 90, Some(0))
619        );
620        assert_eq!(
621            90,
622            effective_gas_setup(TransactionType::Legacy, 90, Some(10))
623        );
624        assert_eq!(
625            120,
626            effective_gas_setup(TransactionType::Legacy, 120, Some(10))
627        );
628        assert_eq!(90, effective_gas_setup(TransactionType::Eip2930, 90, None));
629        assert_eq!(
630            90,
631            effective_gas_setup(TransactionType::Eip2930, 90, Some(0))
632        );
633        assert_eq!(
634            90,
635            effective_gas_setup(TransactionType::Eip2930, 90, Some(10))
636        );
637        assert_eq!(
638            120,
639            effective_gas_setup(TransactionType::Eip2930, 120, Some(10))
640        );
641        assert_eq!(90, effective_gas_setup(TransactionType::Eip1559, 90, None));
642        assert_eq!(
643            90,
644            effective_gas_setup(TransactionType::Eip1559, 90, Some(0))
645        );
646        assert_eq!(
647            90,
648            effective_gas_setup(TransactionType::Eip1559, 90, Some(10))
649        );
650        assert_eq!(
651            110,
652            effective_gas_setup(TransactionType::Eip1559, 120, Some(10))
653        );
654        assert_eq!(90, effective_gas_setup(TransactionType::Eip4844, 90, None));
655        assert_eq!(
656            90,
657            effective_gas_setup(TransactionType::Eip4844, 90, Some(0))
658        );
659        assert_eq!(
660            90,
661            effective_gas_setup(TransactionType::Eip4844, 90, Some(10))
662        );
663        assert_eq!(
664            110,
665            effective_gas_setup(TransactionType::Eip4844, 120, Some(10))
666        );
667        assert_eq!(90, effective_gas_setup(TransactionType::Eip7702, 90, None));
668        assert_eq!(
669            90,
670            effective_gas_setup(TransactionType::Eip7702, 90, Some(0))
671        );
672        assert_eq!(
673            90,
674            effective_gas_setup(TransactionType::Eip7702, 90, Some(10))
675        );
676        assert_eq!(
677            110,
678            effective_gas_setup(TransactionType::Eip7702, 120, Some(10))
679        );
680    }
681}