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
104impl From<MaybeImpersonatedTransaction<Self>> for FoundryTxEnvelope {
105    fn from(value: MaybeImpersonatedTransaction<Self>) -> Self {
106        value.transaction
107    }
108}
109
110impl<T> From<T> for MaybeImpersonatedTransaction<T> {
111    fn from(value: T) -> Self {
112        Self::new(value)
113    }
114}
115
116impl<T: Decodable> Decodable for MaybeImpersonatedTransaction<T> {
117    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
118        T::decode(buf).map(Self::new)
119    }
120}
121
122impl<T> AsRef<T> for MaybeImpersonatedTransaction<T> {
123    fn as_ref(&self) -> &T {
124        &self.transaction
125    }
126}
127
128impl<T> Deref for MaybeImpersonatedTransaction<T> {
129    type Target = T;
130
131    fn deref(&self) -> &Self::Target {
132        &self.transaction
133    }
134}
135
136/// Queued transaction
137#[derive(Clone, Debug, PartialEq, Eq)]
138pub struct PendingTransaction<T> {
139    /// The actual transaction
140    pub transaction: MaybeImpersonatedTransaction<T>,
141    /// the recovered sender of this transaction
142    sender: Address,
143    /// hash of `transaction`, so it can easily be reused with encoding and hashing again
144    hash: TxHash,
145}
146
147impl<T> PendingTransaction<T> {
148    pub const fn hash(&self) -> &TxHash {
149        &self.hash
150    }
151
152    pub const fn sender(&self) -> &Address {
153        &self.sender
154    }
155}
156
157impl<T: SignerRecoverable + TxHashRef + Encodable> PendingTransaction<T> {
158    pub fn new(transaction: T) -> Result<Self, RecoveryError> {
159        let transaction = MaybeImpersonatedTransaction::new(transaction);
160        let sender = transaction.recover()?;
161        let hash = transaction.hash();
162        Ok(Self { transaction, sender, hash })
163    }
164
165    pub fn with_impersonated(transaction: T, sender: Address) -> Self {
166        let transaction = MaybeImpersonatedTransaction::impersonated(transaction, sender);
167        let hash = transaction.hash();
168        Self { transaction, sender, hash }
169    }
170
171    /// Converts a [`MaybeImpersonatedTransaction`] into a [`PendingTransaction`].
172    pub fn from_maybe_impersonated(
173        transaction: MaybeImpersonatedTransaction<T>,
174    ) -> Result<Self, RecoveryError> {
175        if let Some(impersonated) = transaction.impersonated_sender {
176            Ok(Self::with_impersonated(transaction.transaction, impersonated))
177        } else {
178            Self::new(transaction.transaction)
179        }
180    }
181}
182
183impl<T: Transaction> PendingTransaction<T> {
184    pub fn nonce(&self) -> u64 {
185        self.transaction.nonce()
186    }
187}
188
189/// Represents all relevant information of an executed transaction
190#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
191pub struct TransactionInfo {
192    pub transaction_hash: B256,
193    pub transaction_index: u64,
194    pub from: Address,
195    pub to: Option<Address>,
196    pub contract_address: Option<Address>,
197    pub traces: Vec<CallTraceNode>,
198    pub exit: InstructionResult,
199    pub out: Option<Bytes>,
200    pub nonce: u64,
201    pub gas_used: u64,
202}