alloy_evm/
tx.rs

1//! Transaction abstractions for EVM execution.
2//!
3//! This module provides traits and implementations for converting various transaction formats
4//! into a unified transaction environment ([`TxEnv`]) that the EVM can execute. The main purpose
5//! of these traits is to enable flexible transaction input while maintaining type safety.
6
7use alloy_consensus::{
8    crypto::secp256k1, transaction::Recovered, EthereumTxEnvelope, TxEip1559, TxEip2930, TxEip4844,
9    TxEip7702, TxLegacy,
10};
11use alloy_eips::{
12    eip2718::WithEncoded,
13    eip7702::{RecoveredAuthority, RecoveredAuthorization},
14    Typed2718,
15};
16use alloy_primitives::{Address, Bytes, TxKind};
17use revm::{context::TxEnv, context_interface::either::Either};
18
19/// Trait marking types that can be converted into a transaction environment.
20///
21/// This is the primary trait that enables flexible transaction input for the EVM. The EVM's
22/// associated type `Evm::Tx` must implement this trait, and the `transact` method accepts
23/// any type implementing [`IntoTxEnv<Evm::Tx>`](IntoTxEnv).
24///
25/// # Example
26///
27/// ```ignore
28/// // Direct TxEnv usage
29/// let tx_env = TxEnv { caller: address, gas_limit: 100_000, ... };
30/// evm.transact(tx_env)?;
31///
32/// // Using a recovered transaction
33/// let recovered = tx.recover_signer()?;
34/// evm.transact(recovered)?;
35///
36/// // Using a transaction with encoded bytes
37/// let with_encoded = WithEncoded::new(recovered, encoded_bytes);
38/// evm.transact(with_encoded)?;
39/// ```
40pub trait IntoTxEnv<TxEnv> {
41    /// Converts `self` into [`TxEnv`].
42    fn into_tx_env(self) -> TxEnv;
43}
44
45impl IntoTxEnv<Self> for TxEnv {
46    fn into_tx_env(self) -> Self {
47        self
48    }
49}
50
51#[cfg(feature = "op")]
52impl<T> IntoTxEnv<Self> for op_revm::OpTransaction<T>
53where
54    T: revm::context_interface::transaction::Transaction,
55{
56    fn into_tx_env(self) -> Self {
57        self
58    }
59}
60
61/// Helper trait for building a transaction environment from a recovered transaction.
62///
63/// This trait enables the conversion of consensus transaction types (which have been recovered
64/// with their sender address) into the EVM's transaction environment. It's automatically used
65/// when a [`Recovered<T>`] type is passed to the EVM's `transact` method.
66///
67/// The expectation is that any recovered consensus transaction can be converted into the
68/// transaction type that the EVM operates on (typically [`TxEnv`]).
69///
70/// # Implementation
71///
72/// This trait is implemented for all standard Ethereum transaction types ([`TxLegacy`],
73/// [`TxEip2930`], [`TxEip1559`], [`TxEip4844`], [`TxEip7702`]) and transaction envelopes
74/// ([`EthereumTxEnvelope`]).
75///
76/// # Example
77///
78/// ```ignore
79/// // Recover the signer from a transaction
80/// let recovered = tx.recover_signer()?;
81///
82/// // The recovered transaction can now be used with the EVM
83/// // This works because Recovered<T> implements IntoTxEnv when T implements FromRecoveredTx
84/// evm.transact(recovered)?;
85/// ```
86pub trait FromRecoveredTx<Tx> {
87    /// Builds a [`TxEnv`] from a transaction and a sender address.
88    fn from_recovered_tx(tx: &Tx, sender: Address) -> Self;
89}
90
91impl<TxEnv, T> FromRecoveredTx<&T> for TxEnv
92where
93    TxEnv: FromRecoveredTx<T>,
94{
95    fn from_recovered_tx(tx: &&T, sender: Address) -> Self {
96        TxEnv::from_recovered_tx(tx, sender)
97    }
98}
99
100impl<T, TxEnv: FromRecoveredTx<T>> IntoTxEnv<TxEnv> for Recovered<T> {
101    fn into_tx_env(self) -> TxEnv {
102        IntoTxEnv::into_tx_env(&self)
103    }
104}
105
106impl<T, TxEnv: FromRecoveredTx<T>> IntoTxEnv<TxEnv> for &Recovered<T> {
107    fn into_tx_env(self) -> TxEnv {
108        TxEnv::from_recovered_tx(self.inner(), self.signer())
109    }
110}
111
112impl FromRecoveredTx<TxLegacy> for TxEnv {
113    fn from_recovered_tx(tx: &TxLegacy, caller: Address) -> Self {
114        let TxLegacy { chain_id, nonce, gas_price, gas_limit, to, value, input } = tx;
115        Self {
116            tx_type: tx.ty(),
117            caller,
118            gas_limit: *gas_limit,
119            gas_price: *gas_price,
120            kind: *to,
121            value: *value,
122            data: input.clone(),
123            nonce: *nonce,
124            chain_id: *chain_id,
125            ..Default::default()
126        }
127    }
128}
129
130impl FromTxWithEncoded<TxLegacy> for TxEnv {
131    fn from_encoded_tx(tx: &TxLegacy, sender: Address, _encoded: Bytes) -> Self {
132        Self::from_recovered_tx(tx, sender)
133    }
134}
135
136impl FromRecoveredTx<TxEip2930> for TxEnv {
137    fn from_recovered_tx(tx: &TxEip2930, caller: Address) -> Self {
138        let TxEip2930 { chain_id, nonce, gas_price, gas_limit, to, value, access_list, input } = tx;
139        Self {
140            tx_type: tx.ty(),
141            caller,
142            gas_limit: *gas_limit,
143            gas_price: *gas_price,
144            kind: *to,
145            value: *value,
146            data: input.clone(),
147            chain_id: Some(*chain_id),
148            nonce: *nonce,
149            access_list: access_list.clone(),
150            ..Default::default()
151        }
152    }
153}
154
155impl FromTxWithEncoded<TxEip2930> for TxEnv {
156    fn from_encoded_tx(tx: &TxEip2930, sender: Address, _encoded: Bytes) -> Self {
157        Self::from_recovered_tx(tx, sender)
158    }
159}
160
161impl FromRecoveredTx<TxEip1559> for TxEnv {
162    fn from_recovered_tx(tx: &TxEip1559, caller: Address) -> Self {
163        let TxEip1559 {
164            chain_id,
165            nonce,
166            gas_limit,
167            to,
168            value,
169            input,
170            max_fee_per_gas,
171            max_priority_fee_per_gas,
172            access_list,
173        } = tx;
174        Self {
175            tx_type: tx.ty(),
176            caller,
177            gas_limit: *gas_limit,
178            gas_price: *max_fee_per_gas,
179            kind: *to,
180            value: *value,
181            data: input.clone(),
182            nonce: *nonce,
183            chain_id: Some(*chain_id),
184            gas_priority_fee: Some(*max_priority_fee_per_gas),
185            access_list: access_list.clone(),
186            ..Default::default()
187        }
188    }
189}
190
191impl FromTxWithEncoded<TxEip1559> for TxEnv {
192    fn from_encoded_tx(tx: &TxEip1559, sender: Address, _encoded: Bytes) -> Self {
193        Self::from_recovered_tx(tx, sender)
194    }
195}
196
197impl FromRecoveredTx<TxEip4844> for TxEnv {
198    fn from_recovered_tx(tx: &TxEip4844, caller: Address) -> Self {
199        let TxEip4844 {
200            chain_id,
201            nonce,
202            gas_limit,
203            to,
204            value,
205            input,
206            max_fee_per_gas,
207            max_priority_fee_per_gas,
208            access_list,
209            blob_versioned_hashes,
210            max_fee_per_blob_gas,
211        } = tx;
212        Self {
213            tx_type: tx.ty(),
214            caller,
215            gas_limit: *gas_limit,
216            gas_price: *max_fee_per_gas,
217            kind: TxKind::Call(*to),
218            value: *value,
219            data: input.clone(),
220            nonce: *nonce,
221            chain_id: Some(*chain_id),
222            gas_priority_fee: Some(*max_priority_fee_per_gas),
223            access_list: access_list.clone(),
224            blob_hashes: blob_versioned_hashes.clone(),
225            max_fee_per_blob_gas: *max_fee_per_blob_gas,
226            ..Default::default()
227        }
228    }
229}
230
231impl FromTxWithEncoded<TxEip4844> for TxEnv {
232    fn from_encoded_tx(tx: &TxEip4844, sender: Address, _encoded: Bytes) -> Self {
233        Self::from_recovered_tx(tx, sender)
234    }
235}
236
237impl FromRecoveredTx<TxEip7702> for TxEnv {
238    fn from_recovered_tx(tx: &TxEip7702, caller: Address) -> Self {
239        let TxEip7702 {
240            chain_id,
241            nonce,
242            gas_limit,
243            to,
244            value,
245            input,
246            max_fee_per_gas,
247            max_priority_fee_per_gas,
248            access_list,
249            authorization_list,
250        } = tx;
251        Self {
252            tx_type: tx.ty(),
253            caller,
254            gas_limit: *gas_limit,
255            gas_price: *max_fee_per_gas,
256            kind: TxKind::Call(*to),
257            value: *value,
258            data: input.clone(),
259            nonce: *nonce,
260            chain_id: Some(*chain_id),
261            gas_priority_fee: Some(*max_priority_fee_per_gas),
262            access_list: access_list.clone(),
263            authorization_list: authorization_list
264                .iter()
265                .map(|auth| {
266                    Either::Right(RecoveredAuthorization::new_unchecked(
267                        auth.inner().clone(),
268                        auth.signature()
269                            .ok()
270                            .and_then(|signature| {
271                                secp256k1::recover_signer(&signature, auth.signature_hash()).ok()
272                            })
273                            .map_or(RecoveredAuthority::Invalid, RecoveredAuthority::Valid),
274                    ))
275                })
276                .collect(),
277            ..Default::default()
278        }
279    }
280}
281
282impl FromTxWithEncoded<TxEip7702> for TxEnv {
283    fn from_encoded_tx(tx: &TxEip7702, sender: Address, _encoded: Bytes) -> Self {
284        Self::from_recovered_tx(tx, sender)
285    }
286}
287
288/// Helper trait to abstract over different [`Recovered<T>`] implementations.
289///
290/// Implemented for [`Recovered<T>`], `Recovered<&T>`, `&Recovered<T>`, `&Recovered<&T>`
291#[auto_impl::auto_impl(&)]
292pub trait RecoveredTx<T> {
293    /// Returns the transaction.
294    fn tx(&self) -> &T;
295
296    /// Returns the signer of the transaction.
297    fn signer(&self) -> &Address;
298}
299
300impl<T> RecoveredTx<T> for Recovered<&T> {
301    fn tx(&self) -> &T {
302        self.inner()
303    }
304
305    fn signer(&self) -> &Address {
306        self.signer_ref()
307    }
308}
309
310impl<T> RecoveredTx<T> for Recovered<T> {
311    fn tx(&self) -> &T {
312        self.inner()
313    }
314
315    fn signer(&self) -> &Address {
316        self.signer_ref()
317    }
318}
319
320impl<Tx, T: RecoveredTx<Tx>> RecoveredTx<Tx> for WithEncoded<T> {
321    fn tx(&self) -> &Tx {
322        self.1.tx()
323    }
324
325    fn signer(&self) -> &Address {
326        self.1.signer()
327    }
328}
329
330/// Helper trait for building a transaction environment from a transaction with its encoded form.
331///
332/// This trait enables the conversion of consensus transaction types along with their EIP-2718
333/// encoded bytes into the EVM's transaction environment. It's automatically used when a
334/// [`WithEncoded<Recovered<T>>`](WithEncoded) type is passed to the EVM's `transact` method.
335///
336/// The main purpose of this trait is to allow preserving the original encoded transaction data
337/// alongside the parsed transaction, which can be useful for:
338/// - Signature verification
339/// - Transaction hash computation
340/// - Re-encoding for network propagation
341/// - Optimism transaction handling (which requires encoded data, for Data availability costs).
342///
343/// # Implementation
344///
345/// Most implementations simply delegate to [`FromRecoveredTx`], ignoring the encoded bytes.
346/// However, specialized implementations (like Optimism's `OpTransaction`) may use the encoded
347/// data for additional functionality.
348///
349/// # Example
350///
351/// ```ignore
352/// // Create a transaction with its encoded form
353/// let encoded_bytes = tx.encoded_2718();
354/// let recovered = tx.recover_signer()?;
355/// let with_encoded = WithEncoded::new(recovered, encoded_bytes);
356///
357/// // The transaction with encoded data can be used with the EVM
358/// evm.transact(with_encoded)?;
359/// ```
360pub trait FromTxWithEncoded<Tx> {
361    /// Builds a [`TxEnv`] from a transaction, its sender, and encoded transaction bytes.
362    fn from_encoded_tx(tx: &Tx, sender: Address, encoded: Bytes) -> Self;
363}
364
365impl<TxEnv, T> FromTxWithEncoded<&T> for TxEnv
366where
367    TxEnv: FromTxWithEncoded<T>,
368{
369    fn from_encoded_tx(tx: &&T, sender: Address, encoded: Bytes) -> Self {
370        TxEnv::from_encoded_tx(tx, sender, encoded)
371    }
372}
373
374impl<T, TxEnv: FromTxWithEncoded<T>> IntoTxEnv<TxEnv> for WithEncoded<Recovered<T>> {
375    fn into_tx_env(self) -> TxEnv {
376        let recovered = &self.1;
377        TxEnv::from_encoded_tx(recovered.inner(), recovered.signer(), self.encoded_bytes().clone())
378    }
379}
380
381impl<T, TxEnv: FromTxWithEncoded<T>> IntoTxEnv<TxEnv> for &WithEncoded<Recovered<T>> {
382    fn into_tx_env(self) -> TxEnv {
383        let recovered = &self.1;
384        TxEnv::from_encoded_tx(recovered.inner(), recovered.signer(), self.encoded_bytes().clone())
385    }
386}
387
388impl<T, TxEnv: FromTxWithEncoded<T>> IntoTxEnv<TxEnv> for WithEncoded<&Recovered<T>> {
389    fn into_tx_env(self) -> TxEnv {
390        TxEnv::from_encoded_tx(self.value(), *self.value().signer(), self.encoded_bytes().clone())
391    }
392}
393
394impl<T, TxEnv: FromTxWithEncoded<T>> IntoTxEnv<TxEnv> for &WithEncoded<&Recovered<T>> {
395    fn into_tx_env(self) -> TxEnv {
396        TxEnv::from_encoded_tx(self.value(), *self.value().signer(), self.encoded_bytes().clone())
397    }
398}
399
400impl<Eip4844: AsRef<TxEip4844>> FromTxWithEncoded<EthereumTxEnvelope<Eip4844>> for TxEnv {
401    fn from_encoded_tx(tx: &EthereumTxEnvelope<Eip4844>, caller: Address, encoded: Bytes) -> Self {
402        match tx {
403            EthereumTxEnvelope::Legacy(tx) => Self::from_encoded_tx(tx.tx(), caller, encoded),
404            EthereumTxEnvelope::Eip1559(tx) => Self::from_encoded_tx(tx.tx(), caller, encoded),
405            EthereumTxEnvelope::Eip2930(tx) => Self::from_encoded_tx(tx.tx(), caller, encoded),
406            EthereumTxEnvelope::Eip4844(tx) => {
407                Self::from_encoded_tx(tx.tx().as_ref(), caller, encoded)
408            }
409            EthereumTxEnvelope::Eip7702(tx) => Self::from_encoded_tx(tx.tx(), caller, encoded),
410        }
411    }
412}
413
414impl<Eip4844: AsRef<TxEip4844>> FromRecoveredTx<EthereumTxEnvelope<Eip4844>> for TxEnv {
415    fn from_recovered_tx(tx: &EthereumTxEnvelope<Eip4844>, sender: Address) -> Self {
416        match tx {
417            EthereumTxEnvelope::Legacy(tx) => Self::from_recovered_tx(tx.tx(), sender),
418            EthereumTxEnvelope::Eip1559(tx) => Self::from_recovered_tx(tx.tx(), sender),
419            EthereumTxEnvelope::Eip2930(tx) => Self::from_recovered_tx(tx.tx(), sender),
420            EthereumTxEnvelope::Eip4844(tx) => Self::from_recovered_tx(tx.tx().as_ref(), sender),
421            EthereumTxEnvelope::Eip7702(tx) => Self::from_recovered_tx(tx.tx(), sender),
422        }
423    }
424}
425
426#[cfg(feature = "op")]
427mod op {
428    use super::*;
429    use alloy_eips::{Encodable2718, Typed2718};
430    use alloy_primitives::{Address, Bytes};
431    use op_alloy_consensus::{OpTxEnvelope, TxDeposit};
432    use op_revm::{transaction::deposit::DepositTransactionParts, OpTransaction};
433    use revm::context::TxEnv;
434
435    impl FromRecoveredTx<OpTxEnvelope> for TxEnv {
436        fn from_recovered_tx(tx: &OpTxEnvelope, caller: Address) -> Self {
437            match tx {
438                OpTxEnvelope::Legacy(tx) => Self::from_recovered_tx(tx.tx(), caller),
439                OpTxEnvelope::Eip1559(tx) => Self::from_recovered_tx(tx.tx(), caller),
440                OpTxEnvelope::Eip2930(tx) => Self::from_recovered_tx(tx.tx(), caller),
441                OpTxEnvelope::Eip7702(tx) => Self::from_recovered_tx(tx.tx(), caller),
442                OpTxEnvelope::Deposit(tx) => Self::from_recovered_tx(tx.inner(), caller),
443            }
444        }
445    }
446
447    impl FromRecoveredTx<TxDeposit> for TxEnv {
448        fn from_recovered_tx(tx: &TxDeposit, caller: Address) -> Self {
449            let TxDeposit {
450                to,
451                value,
452                gas_limit,
453                input,
454                source_hash: _,
455                from: _,
456                mint: _,
457                is_system_transaction: _,
458            } = tx;
459            Self {
460                tx_type: tx.ty(),
461                caller,
462                gas_limit: *gas_limit,
463                kind: *to,
464                value: *value,
465                data: input.clone(),
466                ..Default::default()
467            }
468        }
469    }
470
471    impl FromTxWithEncoded<OpTxEnvelope> for TxEnv {
472        fn from_encoded_tx(tx: &OpTxEnvelope, caller: Address, _encoded: Bytes) -> Self {
473            Self::from_recovered_tx(tx, caller)
474        }
475    }
476
477    impl FromTxWithEncoded<OpTxEnvelope> for OpTransaction<TxEnv> {
478        fn from_encoded_tx(tx: &OpTxEnvelope, caller: Address, encoded: Bytes) -> Self {
479            let base = TxEnv::from_recovered_tx(tx, caller);
480
481            let deposit = if let OpTxEnvelope::Deposit(tx) = tx {
482                DepositTransactionParts {
483                    source_hash: tx.source_hash,
484                    mint: Some(tx.mint),
485                    is_system_transaction: tx.is_system_transaction,
486                }
487            } else {
488                Default::default()
489            };
490
491            Self { base, enveloped_tx: Some(encoded), deposit }
492        }
493    }
494
495    impl FromRecoveredTx<OpTxEnvelope> for OpTransaction<TxEnv> {
496        fn from_recovered_tx(tx: &OpTxEnvelope, sender: Address) -> Self {
497            let encoded = tx.encoded_2718();
498            Self::from_encoded_tx(tx, sender, encoded.into())
499        }
500    }
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506
507    struct MyTxEnv;
508    struct MyTransaction;
509
510    impl IntoTxEnv<Self> for MyTxEnv {
511        fn into_tx_env(self) -> Self {
512            self
513        }
514    }
515
516    impl FromRecoveredTx<MyTransaction> for MyTxEnv {
517        fn from_recovered_tx(_tx: &MyTransaction, _sender: Address) -> Self {
518            Self
519        }
520    }
521
522    impl FromTxWithEncoded<MyTransaction> for MyTxEnv {
523        fn from_encoded_tx(_tx: &MyTransaction, _sender: Address, _encoded: Bytes) -> Self {
524            Self
525        }
526    }
527
528    const fn assert_env<T: IntoTxEnv<MyTxEnv>>() {}
529    const fn assert_recoverable<T: RecoveredTx<MyTransaction>>() {}
530
531    #[test]
532    const fn test_into_tx_env() {
533        assert_env::<MyTxEnv>();
534        assert_env::<&Recovered<MyTransaction>>();
535        assert_env::<&Recovered<&MyTransaction>>();
536    }
537
538    #[test]
539    const fn test_into_encoded_tx_env() {
540        assert_env::<WithEncoded<Recovered<MyTransaction>>>();
541        assert_env::<&WithEncoded<Recovered<MyTransaction>>>();
542
543        assert_recoverable::<Recovered<MyTransaction>>();
544        assert_recoverable::<WithEncoded<Recovered<MyTransaction>>>();
545    }
546}