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::{eip7825, 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}
90
91impl Default for TxEnv {
92    fn default() -> Self {
93        Self::builder().build().unwrap()
94    }
95}
96
97/// Error type for deriving transaction type used as error in [`TxEnv::derive_tx_type`] function.
98#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100pub enum DeriveTxTypeError {
101    /// Missing target for EIP-4844
102    MissingTargetForEip4844,
103    /// Missing target for EIP-7702
104    MissingTargetForEip7702,
105    /// Missing target for EIP-7873
106    MissingTargetForEip7873,
107}
108
109impl TxEnv {
110    /// Creates a new TxEnv with benchmark-specific values.
111    pub fn new_bench() -> Self {
112        Self {
113            caller: BENCH_CALLER,
114            kind: TxKind::Call(BENCH_TARGET),
115            gas_limit: 1_000_000_000,
116            ..Default::default()
117        }
118    }
119
120    /// Derives tx type from transaction fields and sets it to `tx_type`.
121    /// Returns error in case some fields were not set correctly.
122    pub fn derive_tx_type(&mut self) -> Result<(), DeriveTxTypeError> {
123        if !self.access_list.0.is_empty() {
124            self.tx_type = TransactionType::Eip2930 as u8;
125        }
126
127        if self.gas_priority_fee.is_some() {
128            self.tx_type = TransactionType::Eip1559 as u8;
129        }
130
131        if !self.blob_hashes.is_empty() || self.max_fee_per_blob_gas > 0 {
132            if let TxKind::Call(_) = self.kind {
133                self.tx_type = TransactionType::Eip4844 as u8;
134                return Ok(());
135            } else {
136                return Err(DeriveTxTypeError::MissingTargetForEip4844);
137            }
138        }
139
140        if !self.authorization_list.is_empty() {
141            if let TxKind::Call(_) = self.kind {
142                self.tx_type = TransactionType::Eip7702 as u8;
143                return Ok(());
144            } else {
145                return Err(DeriveTxTypeError::MissingTargetForEip7702);
146            }
147        }
148        Ok(())
149    }
150
151    /// Insert a list of signed authorizations into the authorization list.
152    pub fn set_signed_authorization(&mut self, auth: Vec<SignedAuthorization>) {
153        self.authorization_list = auth.into_iter().map(Either::Left).collect();
154    }
155
156    /// Insert a list of recovered authorizations into the authorization list.
157    pub fn set_recovered_authorization(&mut self, auth: Vec<RecoveredAuthorization>) {
158        self.authorization_list = auth.into_iter().map(Either::Right).collect();
159    }
160}
161
162impl Transaction for TxEnv {
163    type AccessListItem<'a> = &'a AccessListItem;
164    type Authorization<'a> = &'a Either<SignedAuthorization, RecoveredAuthorization>;
165
166    fn tx_type(&self) -> u8 {
167        self.tx_type
168    }
169
170    fn kind(&self) -> TxKind {
171        self.kind
172    }
173
174    fn caller(&self) -> Address {
175        self.caller
176    }
177
178    fn gas_limit(&self) -> u64 {
179        self.gas_limit
180    }
181
182    fn gas_price(&self) -> u128 {
183        self.gas_price
184    }
185
186    fn value(&self) -> U256 {
187        self.value
188    }
189
190    fn nonce(&self) -> u64 {
191        self.nonce
192    }
193
194    fn chain_id(&self) -> Option<u64> {
195        self.chain_id
196    }
197
198    fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
199        Some(self.access_list.0.iter())
200    }
201
202    fn max_fee_per_gas(&self) -> u128 {
203        self.gas_price
204    }
205
206    fn max_fee_per_blob_gas(&self) -> u128 {
207        self.max_fee_per_blob_gas
208    }
209
210    fn authorization_list_len(&self) -> usize {
211        self.authorization_list.len()
212    }
213
214    fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
215        self.authorization_list.iter()
216    }
217
218    fn input(&self) -> &Bytes {
219        &self.data
220    }
221
222    fn blob_versioned_hashes(&self) -> &[B256] {
223        &self.blob_hashes
224    }
225
226    fn max_priority_fee_per_gas(&self) -> Option<u128> {
227        self.gas_priority_fee
228    }
229}
230
231/// Builder for constructing [`TxEnv`] instances
232#[derive(Default, Debug)]
233pub struct TxEnvBuilder {
234    tx_type: Option<u8>,
235    caller: Address,
236    gas_limit: u64,
237    gas_price: u128,
238    kind: TxKind,
239    value: U256,
240    data: Bytes,
241    nonce: u64,
242    chain_id: Option<u64>,
243    access_list: AccessList,
244    gas_priority_fee: Option<u128>,
245    blob_hashes: Vec<B256>,
246    max_fee_per_blob_gas: u128,
247    authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
248}
249
250impl TxEnvBuilder {
251    /// Create a new builder with default values
252    pub fn new() -> Self {
253        Self {
254            tx_type: None,
255            caller: Address::default(),
256            gas_limit: eip7825::TX_GAS_LIMIT_CAP,
257            gas_price: 0,
258            kind: TxKind::Call(Address::default()),
259            value: U256::ZERO,
260            data: Bytes::default(),
261            nonce: 0,
262            chain_id: Some(1), // Mainnet chain ID is 1
263            access_list: Default::default(),
264            gas_priority_fee: None,
265            blob_hashes: Vec::new(),
266            max_fee_per_blob_gas: 0,
267            authorization_list: Vec::new(),
268        }
269    }
270
271    /// Set the transaction type
272    pub fn tx_type(mut self, tx_type: Option<u8>) -> Self {
273        self.tx_type = tx_type;
274        self
275    }
276
277    /// Set the caller address
278    pub fn caller(mut self, caller: Address) -> Self {
279        self.caller = caller;
280        self
281    }
282
283    /// Set the gas limit
284    pub fn gas_limit(mut self, gas_limit: u64) -> Self {
285        self.gas_limit = gas_limit;
286        self
287    }
288
289    /// Set the max fee per gas.
290    pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
291        self.gas_price = max_fee_per_gas;
292        self
293    }
294
295    /// Set the gas price
296    pub fn gas_price(mut self, gas_price: u128) -> Self {
297        self.gas_price = gas_price;
298        self
299    }
300
301    /// Set the transaction kind
302    pub fn kind(mut self, kind: TxKind) -> Self {
303        self.kind = kind;
304        self
305    }
306
307    /// Set the transaction value
308    pub fn value(mut self, value: U256) -> Self {
309        self.value = value;
310        self
311    }
312
313    /// Set the transaction data
314    pub fn data(mut self, data: Bytes) -> Self {
315        self.data = data;
316        self
317    }
318
319    /// Set the transaction nonce
320    pub fn nonce(mut self, nonce: u64) -> Self {
321        self.nonce = nonce;
322        self
323    }
324
325    /// Set the chain ID
326    pub fn chain_id(mut self, chain_id: Option<u64>) -> Self {
327        self.chain_id = chain_id;
328        self
329    }
330
331    /// Set the access list
332    pub fn access_list(mut self, access_list: AccessList) -> Self {
333        self.access_list = access_list;
334        self
335    }
336
337    /// Set the gas priority fee
338    pub fn gas_priority_fee(mut self, gas_priority_fee: Option<u128>) -> Self {
339        self.gas_priority_fee = gas_priority_fee;
340        self
341    }
342
343    /// Set the blob hashes
344    pub fn blob_hashes(mut self, blob_hashes: Vec<B256>) -> Self {
345        self.blob_hashes = blob_hashes;
346        self
347    }
348
349    /// Set the max fee per blob gas
350    pub fn max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self {
351        self.max_fee_per_blob_gas = max_fee_per_blob_gas;
352        self
353    }
354
355    /// Set the authorization list
356    pub fn authorization_list(
357        mut self,
358        authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
359    ) -> Self {
360        self.authorization_list = authorization_list;
361        self
362    }
363
364    /// Build the final [`TxEnv`] with default values for missing fields.
365    pub fn build_fill(mut self) -> TxEnv {
366        let tx_type_not_set = self.tx_type.is_some();
367        if let Some(tx_type) = self.tx_type {
368            match TransactionType::from(tx_type) {
369                TransactionType::Legacy => {
370                    // do nothing
371                }
372                TransactionType::Eip2930 => {
373                    // do nothing, all fields are set. Access list can be empty.
374                }
375                TransactionType::Eip1559 => {
376                    // gas priority fee is required
377                    if self.gas_priority_fee.is_none() {
378                        self.gas_priority_fee = Some(0);
379                    }
380                }
381                TransactionType::Eip4844 => {
382                    // gas priority fee is required
383                    if self.gas_priority_fee.is_none() {
384                        self.gas_priority_fee = Some(0);
385                    }
386
387                    // blob hashes can be empty
388                    if self.blob_hashes.is_empty() {
389                        self.blob_hashes = vec![B256::default()];
390                    }
391
392                    // target is required
393                    if !self.kind.is_call() {
394                        self.kind = TxKind::Call(Address::default());
395                    }
396                }
397                TransactionType::Eip7702 => {
398                    // gas priority fee is required
399                    if self.gas_priority_fee.is_none() {
400                        self.gas_priority_fee = Some(0);
401                    }
402
403                    // authorization list can be empty
404                    if self.authorization_list.is_empty() {
405                        // add dummy authorization
406                        self.authorization_list =
407                            vec![Either::Right(RecoveredAuthorization::new_unchecked(
408                                Authorization {
409                                    chain_id: U256::from(self.chain_id.unwrap_or(1)),
410                                    address: self.caller,
411                                    nonce: self.nonce,
412                                },
413                                RecoveredAuthority::Invalid,
414                            ))];
415                    }
416
417                    // target is required
418                    if !self.kind.is_call() {
419                        self.kind = TxKind::Call(Address::default());
420                    }
421                }
422                TransactionType::Custom => {
423                    // do nothing
424                }
425            }
426        }
427
428        let mut tx = TxEnv {
429            tx_type: self.tx_type.unwrap_or(0),
430            caller: self.caller,
431            gas_limit: self.gas_limit,
432            gas_price: self.gas_price,
433            kind: self.kind,
434            value: self.value,
435            data: self.data,
436            nonce: self.nonce,
437            chain_id: self.chain_id,
438            access_list: self.access_list,
439            gas_priority_fee: self.gas_priority_fee,
440            blob_hashes: self.blob_hashes,
441            max_fee_per_blob_gas: self.max_fee_per_blob_gas,
442            authorization_list: self.authorization_list,
443        };
444
445        // if tx_type is not set, derive it from fields and fix errors.
446        if tx_type_not_set {
447            match tx.derive_tx_type() {
448                Ok(_) => {}
449                Err(DeriveTxTypeError::MissingTargetForEip4844) => {
450                    tx.kind = TxKind::Call(Address::default());
451                }
452                Err(DeriveTxTypeError::MissingTargetForEip7702) => {
453                    tx.kind = TxKind::Call(Address::default());
454                }
455                Err(DeriveTxTypeError::MissingTargetForEip7873) => {
456                    tx.kind = TxKind::Call(Address::default());
457                }
458            }
459        }
460
461        tx
462    }
463
464    /// Build the final [`TxEnv`], returns error if some fields are wrongly set.
465    /// If it is fine to fill missing fields with default values, use [`TxEnvBuilder::build_fill`] instead.
466    pub fn build(self) -> Result<TxEnv, TxEnvBuildError> {
467        // if tx_type is set, check if all needed fields are set correctly.
468        if let Some(tx_type) = self.tx_type {
469            match TransactionType::from(tx_type) {
470                TransactionType::Legacy => {
471                    // do nothing
472                }
473                TransactionType::Eip2930 => {
474                    // do nothing, all fields are set. Access list can be empty.
475                }
476                TransactionType::Eip1559 => {
477                    // gas priority fee is required
478                    if self.gas_priority_fee.is_none() {
479                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
480                    }
481                }
482                TransactionType::Eip4844 => {
483                    // gas priority fee is required
484                    if self.gas_priority_fee.is_none() {
485                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
486                    }
487
488                    // blob hashes can be empty
489                    if self.blob_hashes.is_empty() {
490                        return Err(TxEnvBuildError::MissingBlobHashesForEip4844);
491                    }
492
493                    // target is required
494                    if !self.kind.is_call() {
495                        return Err(TxEnvBuildError::MissingTargetForEip4844);
496                    }
497                }
498                TransactionType::Eip7702 => {
499                    // gas priority fee is required
500                    if self.gas_priority_fee.is_none() {
501                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
502                    }
503
504                    // authorization list can be empty
505                    if self.authorization_list.is_empty() {
506                        return Err(TxEnvBuildError::MissingAuthorizationListForEip7702);
507                    }
508
509                    // target is required
510                    if !self.kind.is_call() {
511                        return Err(DeriveTxTypeError::MissingTargetForEip4844.into());
512                    }
513                }
514                _ => {
515                    panic!()
516                }
517            }
518        }
519
520        let mut tx = TxEnv {
521            tx_type: self.tx_type.unwrap_or(0),
522            caller: self.caller,
523            gas_limit: self.gas_limit,
524            gas_price: self.gas_price,
525            kind: self.kind,
526            value: self.value,
527            data: self.data,
528            nonce: self.nonce,
529            chain_id: self.chain_id,
530            access_list: self.access_list,
531            gas_priority_fee: self.gas_priority_fee,
532            blob_hashes: self.blob_hashes,
533            max_fee_per_blob_gas: self.max_fee_per_blob_gas,
534            authorization_list: self.authorization_list,
535        };
536
537        // Derive tx type from fields, if some fields are wrongly set it will return an error.
538        tx.derive_tx_type()?;
539
540        Ok(tx)
541    }
542}
543
544/// Error type for building [`TxEnv`]
545#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
546#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
547pub enum TxEnvBuildError {
548    /// Derive tx type error
549    DeriveErr(DeriveTxTypeError),
550    /// Missing priority fee for EIP-1559
551    MissingGasPriorityFeeForEip1559,
552    /// Missing blob hashes for EIP-4844
553    MissingBlobHashesForEip4844,
554    /// Missing authorization list for EIP-7702
555    MissingAuthorizationListForEip7702,
556    /// Missing target for EIP-4844
557    MissingTargetForEip4844,
558}
559
560impl From<DeriveTxTypeError> for TxEnvBuildError {
561    fn from(error: DeriveTxTypeError) -> Self {
562        TxEnvBuildError::DeriveErr(error)
563    }
564}
565
566impl TxEnv {
567    /// Create a new builder for constructing a [`TxEnv`]
568    pub fn builder() -> TxEnvBuilder {
569        TxEnvBuilder::new()
570    }
571
572    /// Create a new builder for constructing a [`TxEnv`] with benchmark-specific values.
573    pub fn builder_for_bench() -> TxEnvBuilder {
574        TxEnv::new_bench().modify()
575    }
576
577    /// Modify the [`TxEnv`] by using builder pattern.
578    pub fn modify(self) -> TxEnvBuilder {
579        let TxEnv {
580            tx_type,
581            caller,
582            gas_limit,
583            gas_price,
584            kind,
585            value,
586            data,
587            nonce,
588            chain_id,
589            access_list,
590            gas_priority_fee,
591            blob_hashes,
592            max_fee_per_blob_gas,
593            authorization_list,
594        } = self;
595
596        TxEnvBuilder::new()
597            .tx_type(Some(tx_type))
598            .caller(caller)
599            .gas_limit(gas_limit)
600            .gas_price(gas_price)
601            .kind(kind)
602            .value(value)
603            .data(data)
604            .nonce(nonce)
605            .chain_id(chain_id)
606            .access_list(access_list)
607            .gas_priority_fee(gas_priority_fee)
608            .blob_hashes(blob_hashes)
609            .max_fee_per_blob_gas(max_fee_per_blob_gas)
610            .authorization_list(authorization_list)
611    }
612}
613
614#[cfg(test)]
615mod tests {
616    use super::*;
617
618    fn effective_gas_setup(
619        tx_type: TransactionType,
620        gas_price: u128,
621        gas_priority_fee: Option<u128>,
622    ) -> u128 {
623        let tx = TxEnv {
624            tx_type: tx_type as u8,
625            gas_price,
626            gas_priority_fee,
627            ..Default::default()
628        };
629        let base_fee = 100;
630        tx.effective_gas_price(base_fee)
631    }
632
633    #[test]
634    fn test_effective_gas_price() {
635        assert_eq!(90, effective_gas_setup(TransactionType::Legacy, 90, None));
636        assert_eq!(
637            90,
638            effective_gas_setup(TransactionType::Legacy, 90, Some(0))
639        );
640        assert_eq!(
641            90,
642            effective_gas_setup(TransactionType::Legacy, 90, Some(10))
643        );
644        assert_eq!(
645            120,
646            effective_gas_setup(TransactionType::Legacy, 120, Some(10))
647        );
648        assert_eq!(90, effective_gas_setup(TransactionType::Eip2930, 90, None));
649        assert_eq!(
650            90,
651            effective_gas_setup(TransactionType::Eip2930, 90, Some(0))
652        );
653        assert_eq!(
654            90,
655            effective_gas_setup(TransactionType::Eip2930, 90, Some(10))
656        );
657        assert_eq!(
658            120,
659            effective_gas_setup(TransactionType::Eip2930, 120, Some(10))
660        );
661        assert_eq!(90, effective_gas_setup(TransactionType::Eip1559, 90, None));
662        assert_eq!(
663            90,
664            effective_gas_setup(TransactionType::Eip1559, 90, Some(0))
665        );
666        assert_eq!(
667            90,
668            effective_gas_setup(TransactionType::Eip1559, 90, Some(10))
669        );
670        assert_eq!(
671            110,
672            effective_gas_setup(TransactionType::Eip1559, 120, Some(10))
673        );
674        assert_eq!(90, effective_gas_setup(TransactionType::Eip4844, 90, None));
675        assert_eq!(
676            90,
677            effective_gas_setup(TransactionType::Eip4844, 90, Some(0))
678        );
679        assert_eq!(
680            90,
681            effective_gas_setup(TransactionType::Eip4844, 90, Some(10))
682        );
683        assert_eq!(
684            110,
685            effective_gas_setup(TransactionType::Eip4844, 120, Some(10))
686        );
687        assert_eq!(90, effective_gas_setup(TransactionType::Eip7702, 90, None));
688        assert_eq!(
689            90,
690            effective_gas_setup(TransactionType::Eip7702, 90, Some(0))
691        );
692        assert_eq!(
693            90,
694            effective_gas_setup(TransactionType::Eip7702, 90, Some(10))
695        );
696        assert_eq!(
697            110,
698            effective_gas_setup(TransactionType::Eip7702, 120, Some(10))
699        );
700    }
701}