Skip to main content

anvil_core/eth/transaction/
mod.rs

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