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