anvil_core/eth/transaction/
mod.rs

1//! Transaction related types
2use alloy_consensus::{
3    Signed, Transaction, TxEnvelope, Typed2718, crypto::RecoveryError, transaction::Recovered,
4};
5
6use alloy_eips::eip2718::Encodable2718;
7use alloy_primitives::{Address, B256, Bytes, TxHash};
8use alloy_rlp::{Decodable, Encodable};
9use alloy_rpc_types::Transaction as RpcTransaction;
10use bytes::BufMut;
11use foundry_evm::traces::CallTraceNode;
12use foundry_primitives::FoundryTxEnvelope;
13use revm::interpreter::InstructionResult;
14use serde::{Deserialize, Serialize};
15use std::ops::Deref;
16
17/// A wrapper for [FoundryTxEnvelope] that allows impersonating accounts.
18///
19/// This is a helper that carries the `impersonated` sender so that the right hash
20/// [FoundryTxEnvelope::impersonated_hash] can be created.
21#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
22pub struct MaybeImpersonatedTransaction {
23    transaction: FoundryTxEnvelope,
24    impersonated_sender: Option<Address>,
25}
26
27impl Typed2718 for MaybeImpersonatedTransaction {
28    fn ty(&self) -> u8 {
29        self.transaction.ty()
30    }
31}
32
33impl MaybeImpersonatedTransaction {
34    /// Creates a new wrapper for the given transaction
35    pub fn new(transaction: FoundryTxEnvelope) -> Self {
36        Self { transaction, impersonated_sender: None }
37    }
38
39    /// Creates a new impersonated transaction wrapper using the given sender
40    pub fn impersonated(transaction: FoundryTxEnvelope, impersonated_sender: Address) -> Self {
41        Self { transaction, impersonated_sender: Some(impersonated_sender) }
42    }
43
44    /// Recovers the Ethereum address which was used to sign the transaction.
45    pub fn recover(&self) -> Result<Address, RecoveryError> {
46        if let Some(sender) = self.impersonated_sender {
47            return Ok(sender);
48        }
49        self.transaction.recover()
50    }
51
52    /// Returns whether the transaction is impersonated
53    pub fn is_impersonated(&self) -> bool {
54        self.impersonated_sender.is_some()
55    }
56
57    /// Returns the hash of the transaction
58    pub fn hash(&self) -> B256 {
59        if let Some(sender) = self.impersonated_sender {
60            return self.transaction.impersonated_hash(sender);
61        }
62        self.transaction.hash()
63    }
64
65    /// Converts the transaction into an [`RpcTransaction`]
66    pub fn into_rpc_transaction(self) -> RpcTransaction {
67        let hash = self.hash();
68        let from = self.recover().unwrap_or_default();
69        let envelope = self.transaction.try_into_eth().expect("cant build deposit transactions");
70
71        // NOTE: we must update the hash because the tx can be impersonated, this requires forcing
72        // the hash
73        let inner_envelope = match envelope {
74            TxEnvelope::Legacy(t) => {
75                let (tx, sig, _) = t.into_parts();
76                TxEnvelope::Legacy(Signed::new_unchecked(tx, sig, hash))
77            }
78            TxEnvelope::Eip2930(t) => {
79                let (tx, sig, _) = t.into_parts();
80                TxEnvelope::Eip2930(Signed::new_unchecked(tx, sig, hash))
81            }
82            TxEnvelope::Eip1559(t) => {
83                let (tx, sig, _) = t.into_parts();
84                TxEnvelope::Eip1559(Signed::new_unchecked(tx, sig, hash))
85            }
86            TxEnvelope::Eip4844(t) => {
87                let (tx, sig, _) = t.into_parts();
88                TxEnvelope::Eip4844(Signed::new_unchecked(tx, sig, hash))
89            }
90            TxEnvelope::Eip7702(t) => {
91                let (tx, sig, _) = t.into_parts();
92                TxEnvelope::Eip7702(Signed::new_unchecked(tx, sig, hash))
93            }
94        };
95
96        RpcTransaction {
97            block_hash: None,
98            block_number: None,
99            transaction_index: None,
100            effective_gas_price: None,
101            inner: Recovered::new_unchecked(inner_envelope, from),
102        }
103    }
104}
105
106impl Encodable2718 for MaybeImpersonatedTransaction {
107    fn encode_2718_len(&self) -> usize {
108        self.transaction.encode_2718_len()
109    }
110
111    fn encode_2718(&self, out: &mut dyn BufMut) {
112        self.transaction.encode_2718(out)
113    }
114}
115
116impl Encodable for MaybeImpersonatedTransaction {
117    fn encode(&self, out: &mut dyn bytes::BufMut) {
118        self.transaction.encode(out)
119    }
120}
121
122impl From<MaybeImpersonatedTransaction> for FoundryTxEnvelope {
123    fn from(value: MaybeImpersonatedTransaction) -> Self {
124        value.transaction
125    }
126}
127
128impl From<FoundryTxEnvelope> for MaybeImpersonatedTransaction {
129    fn from(value: FoundryTxEnvelope) -> Self {
130        Self::new(value)
131    }
132}
133
134impl Decodable for MaybeImpersonatedTransaction {
135    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
136        FoundryTxEnvelope::decode(buf).map(Self::new)
137    }
138}
139
140impl AsRef<FoundryTxEnvelope> for MaybeImpersonatedTransaction {
141    fn as_ref(&self) -> &FoundryTxEnvelope {
142        &self.transaction
143    }
144}
145
146impl Deref for MaybeImpersonatedTransaction {
147    type Target = FoundryTxEnvelope;
148
149    fn deref(&self) -> &Self::Target {
150        &self.transaction
151    }
152}
153
154impl From<MaybeImpersonatedTransaction> for RpcTransaction {
155    fn from(value: MaybeImpersonatedTransaction) -> Self {
156        value.into_rpc_transaction()
157    }
158}
159
160/// Queued transaction
161#[derive(Clone, Debug, PartialEq, Eq)]
162pub struct PendingTransaction {
163    /// The actual transaction
164    pub transaction: MaybeImpersonatedTransaction,
165    /// the recovered sender of this transaction
166    sender: Address,
167    /// hash of `transaction`, so it can easily be reused with encoding and hashing again
168    hash: TxHash,
169}
170
171impl PendingTransaction {
172    pub fn new(transaction: FoundryTxEnvelope) -> Result<Self, RecoveryError> {
173        let sender = transaction.recover()?;
174        let hash = transaction.hash();
175        Ok(Self { transaction: MaybeImpersonatedTransaction::new(transaction), sender, hash })
176    }
177
178    pub fn with_impersonated(transaction: FoundryTxEnvelope, sender: Address) -> Self {
179        let hash = transaction.impersonated_hash(sender);
180        Self {
181            transaction: MaybeImpersonatedTransaction::impersonated(transaction, sender),
182            sender,
183            hash,
184        }
185    }
186
187    /// Converts a [`MaybeImpersonatedTransaction`] into a [`PendingTransaction`].
188    pub fn from_maybe_impersonated(
189        transaction: MaybeImpersonatedTransaction,
190    ) -> Result<Self, RecoveryError> {
191        if let Some(impersonated) = transaction.impersonated_sender {
192            Ok(Self::with_impersonated(transaction.transaction, impersonated))
193        } else {
194            Self::new(transaction.transaction)
195        }
196    }
197
198    pub fn nonce(&self) -> u64 {
199        self.transaction.nonce()
200    }
201
202    pub fn hash(&self) -> &TxHash {
203        &self.hash
204    }
205
206    pub fn sender(&self) -> &Address {
207        &self.sender
208    }
209}
210
211/// Represents all relevant information of an executed transaction
212#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
213pub struct TransactionInfo {
214    pub transaction_hash: B256,
215    pub transaction_index: u64,
216    pub from: Address,
217    pub to: Option<Address>,
218    pub contract_address: Option<Address>,
219    pub traces: Vec<CallTraceNode>,
220    pub exit: InstructionResult,
221    pub out: Option<Bytes>,
222    pub nonce: u64,
223    pub gas_used: u64,
224}