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