anvil_core/eth/transaction/
mod.rs

1//! Transaction related types
2
3use crate::eth::transaction::optimism::DepositTransaction;
4use alloy_consensus::{
5    transaction::{
6        eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar},
7        Recovered, TxEip7702,
8    },
9    Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, TxEip1559, TxEip2930, TxEnvelope, TxLegacy,
10    TxReceipt, Typed2718,
11};
12use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718};
13use alloy_network::{AnyReceiptEnvelope, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope};
14use alloy_primitives::{
15    Address, Bloom, Bytes, Log, PrimitiveSignature, TxHash, TxKind, B256, U256, U64,
16};
17use alloy_rlp::{length_of_length, Decodable, Encodable, Header};
18use alloy_rpc_types::{
19    request::TransactionRequest, trace::otterscan::OtsReceipt, AccessList, ConversionError,
20    Transaction as RpcTransaction, TransactionReceipt,
21};
22use alloy_serde::{OtherFields, WithOtherFields};
23use bytes::BufMut;
24use foundry_evm::traces::CallTraceNode;
25use op_alloy_consensus::{TxDeposit, DEPOSIT_TX_TYPE_ID};
26use revm::{
27    interpreter::InstructionResult,
28    primitives::{OptimismFields, TxEnv},
29};
30use serde::{Deserialize, Serialize};
31use std::ops::{Deref, Mul};
32
33pub mod optimism;
34
35/// Converts a [TransactionRequest] into a [TypedTransactionRequest].
36/// Should be removed once the call builder abstraction for providers is in place.
37pub fn transaction_request_to_typed(
38    tx: WithOtherFields<TransactionRequest>,
39) -> Option<TypedTransactionRequest> {
40    let WithOtherFields::<TransactionRequest> {
41        inner:
42            TransactionRequest {
43                from,
44                to,
45                gas_price,
46                max_fee_per_gas,
47                max_priority_fee_per_gas,
48                max_fee_per_blob_gas,
49                blob_versioned_hashes,
50                gas,
51                value,
52                input,
53                nonce,
54                access_list,
55                sidecar,
56                transaction_type,
57                ..
58            },
59        other,
60    } = tx;
61
62    // Special case: OP-stack deposit tx
63    if transaction_type == Some(0x7E) || has_optimism_fields(&other) {
64        let mint = other.get_deserialized::<U256>("mint")?.map(|m| m.to::<u128>()).ok()?;
65
66        return Some(TypedTransactionRequest::Deposit(TxDeposit {
67            from: from.unwrap_or_default(),
68            source_hash: other.get_deserialized::<B256>("sourceHash")?.ok()?,
69            to: to.unwrap_or_default(),
70            mint: Some(mint),
71            value: value.unwrap_or_default(),
72            gas_limit: gas.unwrap_or_default(),
73            is_system_transaction: other.get_deserialized::<bool>("isSystemTx")?.ok()?,
74            input: input.into_input().unwrap_or_default(),
75        }));
76    }
77
78    match (
79        transaction_type,
80        gas_price,
81        max_fee_per_gas,
82        max_priority_fee_per_gas,
83        access_list.as_ref(),
84        max_fee_per_blob_gas,
85        blob_versioned_hashes.as_ref(),
86        sidecar.as_ref(),
87        to,
88    ) {
89        // legacy transaction
90        (Some(0), _, None, None, None, None, None, None, _) |
91        (None, Some(_), None, None, None, None, None, None, _) => {
92            Some(TypedTransactionRequest::Legacy(TxLegacy {
93                nonce: nonce.unwrap_or_default(),
94                gas_price: gas_price.unwrap_or_default(),
95                gas_limit: gas.unwrap_or_default(),
96                value: value.unwrap_or(U256::ZERO),
97                input: input.into_input().unwrap_or_default(),
98                to: to.unwrap_or_default(),
99                chain_id: None,
100            }))
101        }
102        // EIP2930
103        (Some(1), _, None, None, _, None, None, None, _) |
104        (None, _, None, None, Some(_), None, None, None, _) => {
105            Some(TypedTransactionRequest::EIP2930(TxEip2930 {
106                nonce: nonce.unwrap_or_default(),
107                gas_price: gas_price.unwrap_or_default(),
108                gas_limit: gas.unwrap_or_default(),
109                value: value.unwrap_or(U256::ZERO),
110                input: input.into_input().unwrap_or_default(),
111                to: to.unwrap_or_default(),
112                chain_id: 0,
113                access_list: access_list.unwrap_or_default(),
114            }))
115        }
116        // EIP1559
117        (Some(2), None, _, _, _, _, None, None, _) |
118        (None, None, Some(_), _, _, _, None, None, _) |
119        (None, None, _, Some(_), _, _, None, None, _) |
120        (None, None, None, None, None, _, None, None, _) => {
121            // Empty fields fall back to the canonical transaction schema.
122            Some(TypedTransactionRequest::EIP1559(TxEip1559 {
123                nonce: nonce.unwrap_or_default(),
124                max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
125                max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
126                gas_limit: gas.unwrap_or_default(),
127                value: value.unwrap_or(U256::ZERO),
128                input: input.into_input().unwrap_or_default(),
129                to: to.unwrap_or_default(),
130                chain_id: 0,
131                access_list: access_list.unwrap_or_default(),
132            }))
133        }
134        // EIP4844
135        (Some(3), None, _, _, _, _, Some(_), _, to) => {
136            let tx = TxEip4844 {
137                nonce: nonce.unwrap_or_default(),
138                max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
139                max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
140                max_fee_per_blob_gas: max_fee_per_blob_gas.unwrap_or_default(),
141                gas_limit: gas.unwrap_or_default(),
142                value: value.unwrap_or(U256::ZERO),
143                input: input.into_input().unwrap_or_default(),
144                to: match to.unwrap_or(TxKind::Create) {
145                    TxKind::Call(to) => to,
146                    TxKind::Create => Address::ZERO,
147                },
148                chain_id: 0,
149                access_list: access_list.unwrap_or_default(),
150                blob_versioned_hashes: blob_versioned_hashes.unwrap_or_default(),
151            };
152
153            if let Some(sidecar) = sidecar {
154                Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844WithSidecar(
155                    TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar),
156                )))
157            } else {
158                Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844(tx)))
159            }
160        }
161        _ => None,
162    }
163}
164
165fn has_optimism_fields(other: &OtherFields) -> bool {
166    other.contains_key("sourceHash") &&
167        other.contains_key("mint") &&
168        other.contains_key("isSystemTx")
169}
170
171#[derive(Clone, Debug, PartialEq, Eq)]
172pub enum TypedTransactionRequest {
173    Legacy(TxLegacy),
174    EIP2930(TxEip2930),
175    EIP1559(TxEip1559),
176    EIP4844(TxEip4844Variant),
177    Deposit(TxDeposit),
178}
179
180/// A wrapper for [TypedTransaction] that allows impersonating accounts.
181///
182/// This is a helper that carries the `impersonated` sender so that the right hash
183/// [TypedTransaction::impersonated_hash] can be created.
184#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
185pub struct MaybeImpersonatedTransaction {
186    pub transaction: TypedTransaction,
187    pub impersonated_sender: Option<Address>,
188}
189
190impl MaybeImpersonatedTransaction {
191    /// Creates a new wrapper for the given transaction
192    pub fn new(transaction: TypedTransaction) -> Self {
193        Self { transaction, impersonated_sender: None }
194    }
195
196    /// Creates a new impersonated transaction wrapper using the given sender
197    pub fn impersonated(transaction: TypedTransaction, impersonated_sender: Address) -> Self {
198        Self { transaction, impersonated_sender: Some(impersonated_sender) }
199    }
200
201    /// Recovers the Ethereum address which was used to sign the transaction.
202    pub fn recover(&self) -> Result<Address, alloy_primitives::SignatureError> {
203        if let Some(sender) = self.impersonated_sender {
204            return Ok(sender);
205        }
206        self.transaction.recover()
207    }
208
209    /// Returns whether the transaction is impersonated
210    pub fn is_impersonated(&self) -> bool {
211        self.impersonated_sender.is_some()
212    }
213
214    /// Returns the hash of the transaction
215    pub fn hash(&self) -> B256 {
216        if let Some(sender) = self.impersonated_sender {
217            return self.transaction.impersonated_hash(sender)
218        }
219        self.transaction.hash()
220    }
221}
222
223impl Encodable for MaybeImpersonatedTransaction {
224    fn encode(&self, out: &mut dyn bytes::BufMut) {
225        self.transaction.encode(out)
226    }
227}
228
229impl From<MaybeImpersonatedTransaction> for TypedTransaction {
230    fn from(value: MaybeImpersonatedTransaction) -> Self {
231        value.transaction
232    }
233}
234
235impl From<TypedTransaction> for MaybeImpersonatedTransaction {
236    fn from(value: TypedTransaction) -> Self {
237        Self::new(value)
238    }
239}
240
241impl Decodable for MaybeImpersonatedTransaction {
242    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
243        TypedTransaction::decode(buf).map(Self::new)
244    }
245}
246
247impl AsRef<TypedTransaction> for MaybeImpersonatedTransaction {
248    fn as_ref(&self) -> &TypedTransaction {
249        &self.transaction
250    }
251}
252
253impl Deref for MaybeImpersonatedTransaction {
254    type Target = TypedTransaction;
255
256    fn deref(&self) -> &Self::Target {
257        &self.transaction
258    }
259}
260
261impl From<MaybeImpersonatedTransaction> for RpcTransaction {
262    fn from(value: MaybeImpersonatedTransaction) -> Self {
263        let hash = value.hash();
264        let sender = value.recover().unwrap_or_default();
265        to_alloy_transaction_with_hash_and_sender(value.transaction, hash, sender)
266    }
267}
268
269pub fn to_alloy_transaction_with_hash_and_sender(
270    transaction: TypedTransaction,
271    hash: B256,
272    from: Address,
273) -> RpcTransaction {
274    match transaction {
275        TypedTransaction::Legacy(t) => {
276            let (tx, sig, _) = t.into_parts();
277            RpcTransaction {
278                block_hash: None,
279                block_number: None,
280                transaction_index: None,
281                effective_gas_price: None,
282                inner: Recovered::new_unchecked(
283                    TxEnvelope::Legacy(Signed::new_unchecked(tx, sig, hash)),
284                    from,
285                ),
286            }
287        }
288        TypedTransaction::EIP2930(t) => {
289            let (tx, sig, _) = t.into_parts();
290            RpcTransaction {
291                block_hash: None,
292                block_number: None,
293                transaction_index: None,
294                effective_gas_price: None,
295                inner: Recovered::new_unchecked(
296                    TxEnvelope::Eip2930(Signed::new_unchecked(tx, sig, hash)),
297                    from,
298                ),
299            }
300        }
301        TypedTransaction::EIP1559(t) => {
302            let (tx, sig, _) = t.into_parts();
303            RpcTransaction {
304                block_hash: None,
305                block_number: None,
306                transaction_index: None,
307                effective_gas_price: None,
308                inner: Recovered::new_unchecked(
309                    TxEnvelope::Eip1559(Signed::new_unchecked(tx, sig, hash)),
310                    from,
311                ),
312            }
313        }
314        TypedTransaction::EIP4844(t) => {
315            let (tx, sig, _) = t.into_parts();
316            RpcTransaction {
317                block_hash: None,
318                block_number: None,
319                transaction_index: None,
320                effective_gas_price: None,
321                inner: Recovered::new_unchecked(
322                    TxEnvelope::Eip4844(Signed::new_unchecked(tx, sig, hash)),
323                    from,
324                ),
325            }
326        }
327        TypedTransaction::EIP7702(t) => {
328            let (tx, sig, _) = t.into_parts();
329            RpcTransaction {
330                block_hash: None,
331                block_number: None,
332                transaction_index: None,
333                effective_gas_price: None,
334                inner: Recovered::new_unchecked(
335                    TxEnvelope::Eip7702(Signed::new_unchecked(tx, sig, hash)),
336                    from,
337                ),
338            }
339        }
340        TypedTransaction::Deposit(_t) => {
341            unreachable!("cannot reach here, handled in `transaction_build` ")
342        }
343    }
344}
345
346/// Queued transaction
347#[derive(Clone, Debug, PartialEq, Eq)]
348pub struct PendingTransaction {
349    /// The actual transaction
350    pub transaction: MaybeImpersonatedTransaction,
351    /// the recovered sender of this transaction
352    sender: Address,
353    /// hash of `transaction`, so it can easily be reused with encoding and hashing agan
354    hash: TxHash,
355}
356
357impl PendingTransaction {
358    pub fn new(transaction: TypedTransaction) -> Result<Self, alloy_primitives::SignatureError> {
359        let sender = transaction.recover()?;
360        let hash = transaction.hash();
361        Ok(Self { transaction: MaybeImpersonatedTransaction::new(transaction), sender, hash })
362    }
363
364    pub fn with_impersonated(transaction: TypedTransaction, sender: Address) -> Self {
365        let hash = transaction.impersonated_hash(sender);
366        Self {
367            transaction: MaybeImpersonatedTransaction::impersonated(transaction, sender),
368            sender,
369            hash,
370        }
371    }
372
373    pub fn nonce(&self) -> u64 {
374        self.transaction.nonce()
375    }
376
377    pub fn hash(&self) -> &TxHash {
378        &self.hash
379    }
380
381    pub fn sender(&self) -> &Address {
382        &self.sender
383    }
384
385    /// Converts the [PendingTransaction] into the [TxEnv] context that [`revm`](foundry_evm)
386    /// expects.
387    pub fn to_revm_tx_env(&self) -> TxEnv {
388        fn transact_to(kind: &TxKind) -> TxKind {
389            match kind {
390                TxKind::Call(c) => TxKind::Call(*c),
391                TxKind::Create => TxKind::Create,
392            }
393        }
394
395        let caller = *self.sender();
396        match &self.transaction.transaction {
397            TypedTransaction::Legacy(tx) => {
398                let chain_id = tx.tx().chain_id;
399                let TxLegacy { nonce, gas_price, gas_limit, value, to, input, .. } = tx.tx();
400                TxEnv {
401                    caller,
402                    transact_to: transact_to(to),
403                    data: input.clone(),
404                    chain_id,
405                    nonce: Some(*nonce),
406                    value: (*value),
407                    gas_price: U256::from(*gas_price),
408                    gas_priority_fee: None,
409                    gas_limit: *gas_limit,
410                    access_list: vec![],
411                    ..Default::default()
412                }
413            }
414            TypedTransaction::EIP2930(tx) => {
415                let TxEip2930 {
416                    chain_id,
417                    nonce,
418                    gas_price,
419                    gas_limit,
420                    to,
421                    value,
422                    input,
423                    access_list,
424                    ..
425                } = tx.tx();
426                TxEnv {
427                    caller,
428                    transact_to: transact_to(to),
429                    data: input.clone(),
430                    chain_id: Some(*chain_id),
431                    nonce: Some(*nonce),
432                    value: *value,
433                    gas_price: U256::from(*gas_price),
434                    gas_priority_fee: None,
435                    gas_limit: *gas_limit,
436                    access_list: access_list.clone().into(),
437                    ..Default::default()
438                }
439            }
440            TypedTransaction::EIP1559(tx) => {
441                let TxEip1559 {
442                    chain_id,
443                    nonce,
444                    max_priority_fee_per_gas,
445                    max_fee_per_gas,
446                    gas_limit,
447                    to,
448                    value,
449                    input,
450                    access_list,
451                    ..
452                } = tx.tx();
453                TxEnv {
454                    caller,
455                    transact_to: transact_to(to),
456                    data: input.clone(),
457                    chain_id: Some(*chain_id),
458                    nonce: Some(*nonce),
459                    value: *value,
460                    gas_price: U256::from(*max_fee_per_gas),
461                    gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)),
462                    gas_limit: *gas_limit,
463                    access_list: access_list.clone().into(),
464                    ..Default::default()
465                }
466            }
467            TypedTransaction::EIP4844(tx) => {
468                let TxEip4844 {
469                    chain_id,
470                    nonce,
471                    max_fee_per_blob_gas,
472                    max_fee_per_gas,
473                    max_priority_fee_per_gas,
474                    gas_limit,
475                    to,
476                    value,
477                    input,
478                    access_list,
479                    blob_versioned_hashes,
480                    ..
481                } = tx.tx().tx();
482                TxEnv {
483                    caller,
484                    transact_to: TxKind::Call(*to),
485                    data: input.clone(),
486                    chain_id: Some(*chain_id),
487                    nonce: Some(*nonce),
488                    value: *value,
489                    gas_price: U256::from(*max_fee_per_gas),
490                    gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)),
491                    max_fee_per_blob_gas: Some(U256::from(*max_fee_per_blob_gas)),
492                    blob_hashes: blob_versioned_hashes.clone(),
493                    gas_limit: *gas_limit,
494                    access_list: access_list.clone().into(),
495                    ..Default::default()
496                }
497            }
498            TypedTransaction::EIP7702(tx) => {
499                let TxEip7702 {
500                    chain_id,
501                    nonce,
502                    gas_limit,
503                    max_fee_per_gas,
504                    max_priority_fee_per_gas,
505                    to,
506                    value,
507                    access_list,
508                    authorization_list,
509                    input,
510                } = tx.tx();
511                TxEnv {
512                    caller,
513                    transact_to: TxKind::Call(*to),
514                    data: input.clone(),
515                    chain_id: Some(*chain_id),
516                    nonce: Some(*nonce),
517                    value: *value,
518                    gas_price: U256::from(*max_fee_per_gas),
519                    gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)),
520                    gas_limit: *gas_limit,
521                    access_list: access_list.clone().into(),
522                    authorization_list: Some(authorization_list.clone().into()),
523                    ..Default::default()
524                }
525            }
526            TypedTransaction::Deposit(tx) => {
527                let chain_id = tx.chain_id();
528                let DepositTransaction {
529                    nonce,
530                    source_hash,
531                    gas_limit,
532                    value,
533                    kind,
534                    mint,
535                    input,
536                    is_system_tx,
537                    ..
538                } = tx;
539                TxEnv {
540                    caller,
541                    transact_to: transact_to(kind),
542                    data: input.clone(),
543                    chain_id,
544                    nonce: Some(*nonce),
545                    value: *value,
546                    gas_price: U256::ZERO,
547                    gas_priority_fee: None,
548                    gas_limit: { *gas_limit },
549                    access_list: vec![],
550                    optimism: OptimismFields {
551                        source_hash: Some(*source_hash),
552                        mint: Some(mint.to::<u128>()),
553                        is_system_transaction: Some(*is_system_tx),
554                        enveloped_tx: None,
555                    },
556                    ..Default::default()
557                }
558            }
559        }
560    }
561}
562
563/// Container type for signed, typed transactions.
564#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
565pub enum TypedTransaction {
566    /// Legacy transaction type
567    Legacy(Signed<TxLegacy>),
568    /// EIP-2930 transaction
569    EIP2930(Signed<TxEip2930>),
570    /// EIP-1559 transaction
571    EIP1559(Signed<TxEip1559>),
572    /// EIP-4844 transaction
573    EIP4844(Signed<TxEip4844Variant>),
574    /// EIP-7702 transaction
575    EIP7702(Signed<TxEip7702>),
576    /// op-stack deposit transaction
577    Deposit(DepositTransaction),
578}
579
580/// This is a function that demotes TypedTransaction to TransactionRequest for greater flexibility
581/// over the type.
582///
583/// This function is purely for convenience and specific use cases, e.g. RLP encoded transactions
584/// decode to TypedTransactions where the API over TypedTransctions is quite strict.
585impl TryFrom<TypedTransaction> for TransactionRequest {
586    type Error = ConversionError;
587
588    fn try_from(value: TypedTransaction) -> Result<Self, Self::Error> {
589        let from =
590            value.recover().map_err(|_| ConversionError::Custom("InvalidSignature".to_string()))?;
591        let essentials = value.essentials();
592        let tx_type = value.r#type();
593        Ok(Self {
594            from: Some(from),
595            to: Some(value.kind()),
596            gas_price: essentials.gas_price,
597            max_fee_per_gas: essentials.max_fee_per_gas,
598            max_priority_fee_per_gas: essentials.max_priority_fee_per_gas,
599            max_fee_per_blob_gas: essentials.max_fee_per_blob_gas,
600            gas: Some(essentials.gas_limit),
601            value: Some(essentials.value),
602            input: essentials.input.into(),
603            nonce: Some(essentials.nonce),
604            chain_id: essentials.chain_id,
605            transaction_type: tx_type,
606            ..Default::default()
607        })
608    }
609}
610
611impl TryFrom<AnyRpcTransaction> for TypedTransaction {
612    type Error = ConversionError;
613
614    fn try_from(value: AnyRpcTransaction) -> Result<Self, Self::Error> {
615        let WithOtherFields { inner, .. } = value.0;
616        let from = inner.inner.signer();
617        match inner.inner.into_inner() {
618            AnyTxEnvelope::Ethereum(tx) => match tx {
619                TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
620                TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
621                TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
622                TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
623                TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)),
624            },
625            AnyTxEnvelope::Unknown(mut tx) => {
626                // Try to convert to deposit transaction
627                if tx.ty() == DEPOSIT_TX_TYPE_ID {
628                    let nonce = get_field::<U64>(&tx.inner.fields, "nonce")?;
629                    tx.inner.fields.insert("from".to_string(), serde_json::to_value(from).unwrap());
630                    let deposit_tx =
631                        tx.inner.fields.deserialize_into::<TxDeposit>().map_err(|e| {
632                            ConversionError::Custom(format!(
633                                "Failed to deserialize deposit tx: {e}"
634                            ))
635                        })?;
636
637                    let TxDeposit {
638                        source_hash,
639                        is_system_transaction,
640                        value,
641                        gas_limit,
642                        input,
643                        mint,
644                        from,
645                        to,
646                    } = deposit_tx;
647
648                    let deposit_tx = DepositTransaction {
649                        nonce: nonce.to(),
650                        source_hash,
651                        from,
652                        kind: to,
653                        mint: mint.map(|m| U256::from(m)).unwrap_or_default(),
654                        value,
655                        gas_limit,
656                        is_system_tx: is_system_transaction,
657                        input,
658                    };
659
660                    return Ok(Self::Deposit(deposit_tx));
661                };
662
663                Err(ConversionError::Custom("UnknownTxType".to_string()))
664            }
665        }
666    }
667}
668
669fn get_field<T: serde::de::DeserializeOwned>(
670    fields: &OtherFields,
671    key: &str,
672) -> Result<T, ConversionError> {
673    fields
674        .get_deserialized::<T>(key)
675        .ok_or_else(|| ConversionError::Custom(format!("Missing{key}")))?
676        .map_err(|e| ConversionError::Custom(format!("Failed to deserialize {key}: {e}")))
677}
678
679impl TypedTransaction {
680    /// Returns true if the transaction uses dynamic fees: EIP1559, EIP4844 or EIP7702
681    pub fn is_dynamic_fee(&self) -> bool {
682        matches!(self, Self::EIP1559(_) | Self::EIP4844(_) | Self::EIP7702(_))
683    }
684
685    pub fn gas_price(&self) -> u128 {
686        match self {
687            Self::Legacy(tx) => tx.tx().gas_price,
688            Self::EIP2930(tx) => tx.tx().gas_price,
689            Self::EIP1559(tx) => tx.tx().max_fee_per_gas,
690            Self::EIP4844(tx) => tx.tx().tx().max_fee_per_gas,
691            Self::EIP7702(tx) => tx.tx().max_fee_per_gas,
692            Self::Deposit(_) => 0,
693        }
694    }
695
696    pub fn gas_limit(&self) -> u64 {
697        match self {
698            Self::Legacy(tx) => tx.tx().gas_limit,
699            Self::EIP2930(tx) => tx.tx().gas_limit,
700            Self::EIP1559(tx) => tx.tx().gas_limit,
701            Self::EIP4844(tx) => tx.tx().tx().gas_limit,
702            Self::EIP7702(tx) => tx.tx().gas_limit,
703            Self::Deposit(tx) => tx.gas_limit,
704        }
705    }
706
707    pub fn value(&self) -> U256 {
708        U256::from(match self {
709            Self::Legacy(tx) => tx.tx().value,
710            Self::EIP2930(tx) => tx.tx().value,
711            Self::EIP1559(tx) => tx.tx().value,
712            Self::EIP4844(tx) => tx.tx().tx().value,
713            Self::EIP7702(tx) => tx.tx().value,
714            Self::Deposit(tx) => tx.value,
715        })
716    }
717
718    pub fn data(&self) -> &Bytes {
719        match self {
720            Self::Legacy(tx) => &tx.tx().input,
721            Self::EIP2930(tx) => &tx.tx().input,
722            Self::EIP1559(tx) => &tx.tx().input,
723            Self::EIP4844(tx) => &tx.tx().tx().input,
724            Self::EIP7702(tx) => &tx.tx().input,
725            Self::Deposit(tx) => &tx.input,
726        }
727    }
728
729    /// Returns the transaction type
730    pub fn r#type(&self) -> Option<u8> {
731        match self {
732            Self::Legacy(_) => None,
733            Self::EIP2930(_) => Some(1),
734            Self::EIP1559(_) => Some(2),
735            Self::EIP4844(_) => Some(3),
736            Self::EIP7702(_) => Some(4),
737            Self::Deposit(_) => Some(0x7E),
738        }
739    }
740
741    /// Max cost of the transaction
742    /// It is the gas limit multiplied by the gas price,
743    /// and if the transaction is EIP-4844, the result of (total blob gas cost * max fee per blob
744    /// gas) is also added
745    pub fn max_cost(&self) -> u128 {
746        let mut max_cost = (self.gas_limit() as u128).saturating_mul(self.gas_price());
747
748        if self.is_eip4844() {
749            max_cost = max_cost.saturating_add(
750                self.blob_gas()
751                    .map(|g| g as u128)
752                    .unwrap_or(0)
753                    .mul(self.max_fee_per_blob_gas().unwrap_or(0)),
754            )
755        }
756
757        max_cost
758    }
759
760    pub fn blob_gas(&self) -> Option<u64> {
761        match self {
762            Self::EIP4844(tx) => Some(tx.tx().tx().blob_gas()),
763            _ => None,
764        }
765    }
766
767    pub fn max_fee_per_blob_gas(&self) -> Option<u128> {
768        match self {
769            Self::EIP4844(tx) => Some(tx.tx().tx().max_fee_per_blob_gas),
770            _ => None,
771        }
772    }
773
774    /// Returns a helper type that contains commonly used values as fields
775    pub fn essentials(&self) -> TransactionEssentials {
776        match self {
777            Self::Legacy(t) => TransactionEssentials {
778                kind: t.tx().to,
779                input: t.tx().input.clone(),
780                nonce: t.tx().nonce,
781                gas_limit: t.tx().gas_limit,
782                gas_price: Some(t.tx().gas_price),
783                max_fee_per_gas: None,
784                max_priority_fee_per_gas: None,
785                max_fee_per_blob_gas: None,
786                blob_versioned_hashes: None,
787                value: t.tx().value,
788                chain_id: t.tx().chain_id,
789                access_list: Default::default(),
790            },
791            Self::EIP2930(t) => TransactionEssentials {
792                kind: t.tx().to,
793                input: t.tx().input.clone(),
794                nonce: t.tx().nonce,
795                gas_limit: t.tx().gas_limit,
796                gas_price: Some(t.tx().gas_price),
797                max_fee_per_gas: None,
798                max_priority_fee_per_gas: None,
799                max_fee_per_blob_gas: None,
800                blob_versioned_hashes: None,
801                value: t.tx().value,
802                chain_id: Some(t.tx().chain_id),
803                access_list: t.tx().access_list.clone(),
804            },
805            Self::EIP1559(t) => TransactionEssentials {
806                kind: t.tx().to,
807                input: t.tx().input.clone(),
808                nonce: t.tx().nonce,
809                gas_limit: t.tx().gas_limit,
810                gas_price: None,
811                max_fee_per_gas: Some(t.tx().max_fee_per_gas),
812                max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas),
813                max_fee_per_blob_gas: None,
814                blob_versioned_hashes: None,
815                value: t.tx().value,
816                chain_id: Some(t.tx().chain_id),
817                access_list: t.tx().access_list.clone(),
818            },
819            Self::EIP4844(t) => TransactionEssentials {
820                kind: TxKind::Call(t.tx().tx().to),
821                input: t.tx().tx().input.clone(),
822                nonce: t.tx().tx().nonce,
823                gas_limit: t.tx().tx().gas_limit,
824                gas_price: Some(t.tx().tx().max_fee_per_blob_gas),
825                max_fee_per_gas: Some(t.tx().tx().max_fee_per_gas),
826                max_priority_fee_per_gas: Some(t.tx().tx().max_priority_fee_per_gas),
827                max_fee_per_blob_gas: Some(t.tx().tx().max_fee_per_blob_gas),
828                blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()),
829                value: t.tx().tx().value,
830                chain_id: Some(t.tx().tx().chain_id),
831                access_list: t.tx().tx().access_list.clone(),
832            },
833            Self::EIP7702(t) => TransactionEssentials {
834                kind: TxKind::Call(t.tx().to),
835                input: t.tx().input.clone(),
836                nonce: t.tx().nonce,
837                gas_limit: t.tx().gas_limit,
838                gas_price: Some(t.tx().max_fee_per_gas),
839                max_fee_per_gas: Some(t.tx().max_fee_per_gas),
840                max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas),
841                max_fee_per_blob_gas: None,
842                blob_versioned_hashes: None,
843                value: t.tx().value,
844                chain_id: Some(t.tx().chain_id),
845                access_list: t.tx().access_list.clone(),
846            },
847            Self::Deposit(t) => TransactionEssentials {
848                kind: t.kind,
849                input: t.input.clone(),
850                nonce: t.nonce,
851                gas_limit: t.gas_limit,
852                gas_price: Some(0),
853                max_fee_per_gas: None,
854                max_priority_fee_per_gas: None,
855                max_fee_per_blob_gas: None,
856                blob_versioned_hashes: None,
857                value: t.value,
858                chain_id: t.chain_id(),
859                access_list: Default::default(),
860            },
861        }
862    }
863
864    pub fn nonce(&self) -> u64 {
865        match self {
866            Self::Legacy(t) => t.tx().nonce,
867            Self::EIP2930(t) => t.tx().nonce,
868            Self::EIP1559(t) => t.tx().nonce,
869            Self::EIP4844(t) => t.tx().tx().nonce,
870            Self::EIP7702(t) => t.tx().nonce,
871            Self::Deposit(t) => t.nonce,
872        }
873    }
874
875    pub fn chain_id(&self) -> Option<u64> {
876        match self {
877            Self::Legacy(t) => t.tx().chain_id,
878            Self::EIP2930(t) => Some(t.tx().chain_id),
879            Self::EIP1559(t) => Some(t.tx().chain_id),
880            Self::EIP4844(t) => Some(t.tx().tx().chain_id),
881            Self::EIP7702(t) => Some(t.tx().chain_id),
882            Self::Deposit(t) => t.chain_id(),
883        }
884    }
885
886    pub fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
887        match self {
888            Self::Legacy(tx) => Some(tx),
889            _ => None,
890        }
891    }
892
893    /// Returns true whether this tx is a legacy transaction
894    pub fn is_legacy(&self) -> bool {
895        matches!(self, Self::Legacy(_))
896    }
897
898    /// Returns true whether this tx is a EIP1559 transaction
899    pub fn is_eip1559(&self) -> bool {
900        matches!(self, Self::EIP1559(_))
901    }
902
903    /// Returns true whether this tx is a EIP2930 transaction
904    pub fn is_eip2930(&self) -> bool {
905        matches!(self, Self::EIP2930(_))
906    }
907
908    /// Returns true whether this tx is a EIP4844 transaction
909    pub fn is_eip4844(&self) -> bool {
910        matches!(self, Self::EIP4844(_))
911    }
912
913    /// Returns the hash of the transaction.
914    ///
915    /// Note: If this transaction has the Impersonated signature then this returns a modified unique
916    /// hash. This allows us to treat impersonated transactions as unique.
917    pub fn hash(&self) -> B256 {
918        match self {
919            Self::Legacy(t) => *t.hash(),
920            Self::EIP2930(t) => *t.hash(),
921            Self::EIP1559(t) => *t.hash(),
922            Self::EIP4844(t) => *t.hash(),
923            Self::EIP7702(t) => *t.hash(),
924            Self::Deposit(t) => t.hash(),
925        }
926    }
927
928    /// Returns the hash if the transaction is impersonated (using a fake signature)
929    ///
930    /// This appends the `address` before hashing it
931    pub fn impersonated_hash(&self, sender: Address) -> B256 {
932        let mut buffer = Vec::new();
933        Encodable::encode(self, &mut buffer);
934        buffer.extend_from_slice(sender.as_ref());
935        B256::from_slice(alloy_primitives::utils::keccak256(&buffer).as_slice())
936    }
937
938    /// Recovers the Ethereum address which was used to sign the transaction.
939    pub fn recover(&self) -> Result<Address, alloy_primitives::SignatureError> {
940        match self {
941            Self::Legacy(tx) => tx.recover_signer(),
942            Self::EIP2930(tx) => tx.recover_signer(),
943            Self::EIP1559(tx) => tx.recover_signer(),
944            Self::EIP4844(tx) => tx.recover_signer(),
945            Self::EIP7702(tx) => tx.recover_signer(),
946            Self::Deposit(tx) => tx.recover(),
947        }
948    }
949
950    /// Returns what kind of transaction this is
951    pub fn kind(&self) -> TxKind {
952        match self {
953            Self::Legacy(tx) => tx.tx().to,
954            Self::EIP2930(tx) => tx.tx().to,
955            Self::EIP1559(tx) => tx.tx().to,
956            Self::EIP4844(tx) => TxKind::Call(tx.tx().tx().to),
957            Self::EIP7702(tx) => TxKind::Call(tx.tx().to),
958            Self::Deposit(tx) => tx.kind,
959        }
960    }
961
962    /// Returns the callee if this transaction is a call
963    pub fn to(&self) -> Option<Address> {
964        self.kind().to().copied()
965    }
966
967    /// Returns the Signature of the transaction
968    pub fn signature(&self) -> PrimitiveSignature {
969        match self {
970            Self::Legacy(tx) => *tx.signature(),
971            Self::EIP2930(tx) => *tx.signature(),
972            Self::EIP1559(tx) => *tx.signature(),
973            Self::EIP4844(tx) => *tx.signature(),
974            Self::EIP7702(tx) => *tx.signature(),
975            Self::Deposit(_) => PrimitiveSignature::from_scalars_and_parity(
976                B256::with_last_byte(1),
977                B256::with_last_byte(1),
978                false,
979            ),
980        }
981    }
982}
983
984impl Encodable for TypedTransaction {
985    fn encode(&self, out: &mut dyn bytes::BufMut) {
986        if !self.is_legacy() {
987            Header { list: false, payload_length: self.encode_2718_len() }.encode(out);
988        }
989
990        self.encode_2718(out);
991    }
992}
993
994impl Decodable for TypedTransaction {
995    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
996        let mut h_decode_copy = *buf;
997        let header = alloy_rlp::Header::decode(&mut h_decode_copy)?;
998
999        // Legacy TX
1000        if header.list {
1001            return Ok(TxEnvelope::decode(buf)?.into());
1002        }
1003
1004        // Check byte after header
1005        let ty = *h_decode_copy.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?;
1006
1007        if ty != 0x7E {
1008            Ok(TxEnvelope::decode(buf)?.into())
1009        } else {
1010            Ok(Self::Deposit(DepositTransaction::decode_2718(buf)?))
1011        }
1012    }
1013}
1014
1015impl Typed2718 for TypedTransaction {
1016    fn ty(&self) -> u8 {
1017        self.r#type().unwrap_or(0)
1018    }
1019}
1020
1021impl Encodable2718 for TypedTransaction {
1022    fn encode_2718_len(&self) -> usize {
1023        match self {
1024            Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1025            Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1026            Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1027            Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1028            Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
1029            Self::Deposit(tx) => 1 + tx.length(),
1030        }
1031    }
1032
1033    fn encode_2718(&self, out: &mut dyn BufMut) {
1034        match self {
1035            Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1036            Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1037            Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1038            Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1039            Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
1040            Self::Deposit(tx) => {
1041                tx.encode_2718(out);
1042            }
1043        }
1044    }
1045}
1046
1047impl Decodable2718 for TypedTransaction {
1048    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1049        if ty == 0x7E {
1050            return Ok(Self::Deposit(DepositTransaction::decode(buf)?))
1051        }
1052        match TxEnvelope::typed_decode(ty, buf)? {
1053            TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
1054            TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
1055            TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
1056            TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)),
1057            _ => unreachable!(),
1058        }
1059    }
1060
1061    fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1062        match TxEnvelope::fallback_decode(buf)? {
1063            TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
1064            _ => unreachable!(),
1065        }
1066    }
1067}
1068
1069impl From<TxEnvelope> for TypedTransaction {
1070    fn from(value: TxEnvelope) -> Self {
1071        match value {
1072            TxEnvelope::Legacy(tx) => Self::Legacy(tx),
1073            TxEnvelope::Eip2930(tx) => Self::EIP2930(tx),
1074            TxEnvelope::Eip1559(tx) => Self::EIP1559(tx),
1075            TxEnvelope::Eip4844(tx) => Self::EIP4844(tx),
1076            _ => unreachable!(),
1077        }
1078    }
1079}
1080
1081#[derive(Clone, Debug, PartialEq, Eq)]
1082pub struct TransactionEssentials {
1083    pub kind: TxKind,
1084    pub input: Bytes,
1085    pub nonce: u64,
1086    pub gas_limit: u64,
1087    pub gas_price: Option<u128>,
1088    pub max_fee_per_gas: Option<u128>,
1089    pub max_priority_fee_per_gas: Option<u128>,
1090    pub max_fee_per_blob_gas: Option<u128>,
1091    pub blob_versioned_hashes: Option<Vec<B256>>,
1092    pub value: U256,
1093    pub chain_id: Option<u64>,
1094    pub access_list: AccessList,
1095}
1096
1097/// Represents all relevant information of an executed transaction
1098#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
1099pub struct TransactionInfo {
1100    pub transaction_hash: B256,
1101    pub transaction_index: u64,
1102    pub from: Address,
1103    pub to: Option<Address>,
1104    pub contract_address: Option<Address>,
1105    pub traces: Vec<CallTraceNode>,
1106    pub exit: InstructionResult,
1107    pub out: Option<Bytes>,
1108    pub nonce: u64,
1109    pub gas_used: u64,
1110}
1111
1112#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1113#[serde(rename_all = "camelCase")]
1114pub struct DepositReceipt<T = Receipt<alloy_primitives::Log>> {
1115    #[serde(flatten)]
1116    pub inner: ReceiptWithBloom<T>,
1117    #[serde(default, with = "alloy_serde::quantity::opt")]
1118    pub deposit_nonce: Option<u64>,
1119    #[serde(default, with = "alloy_serde::quantity::opt")]
1120    pub deposit_receipt_version: Option<u64>,
1121}
1122
1123impl DepositReceipt {
1124    fn payload_len(&self) -> usize {
1125        self.inner.receipt.status.length() +
1126            self.inner.receipt.cumulative_gas_used.length() +
1127            self.inner.logs_bloom.length() +
1128            self.inner.receipt.logs.length() +
1129            self.deposit_nonce.map_or(0, |n| n.length()) +
1130            self.deposit_receipt_version.map_or(0, |n| n.length())
1131    }
1132
1133    /// Returns the rlp header for the receipt payload.
1134    fn receipt_rlp_header(&self) -> alloy_rlp::Header {
1135        alloy_rlp::Header { list: true, payload_length: self.payload_len() }
1136    }
1137
1138    /// Encodes the receipt data.
1139    fn encode_fields(&self, out: &mut dyn BufMut) {
1140        self.receipt_rlp_header().encode(out);
1141        self.inner.status().encode(out);
1142        self.inner.receipt.cumulative_gas_used.encode(out);
1143        self.inner.logs_bloom.encode(out);
1144        self.inner.receipt.logs.encode(out);
1145        if let Some(n) = self.deposit_nonce {
1146            n.encode(out);
1147        }
1148        if let Some(n) = self.deposit_receipt_version {
1149            n.encode(out);
1150        }
1151    }
1152
1153    /// Decodes the receipt payload
1154    fn decode_receipt(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1155        let b: &mut &[u8] = &mut &**buf;
1156        let rlp_head = alloy_rlp::Header::decode(b)?;
1157        if !rlp_head.list {
1158            return Err(alloy_rlp::Error::UnexpectedString);
1159        }
1160        let started_len = b.len();
1161        let remaining = |b: &[u8]| rlp_head.payload_length - (started_len - b.len()) > 0;
1162
1163        let status = Decodable::decode(b)?;
1164        let cumulative_gas_used = Decodable::decode(b)?;
1165        let logs_bloom = Decodable::decode(b)?;
1166        let logs: Vec<Log> = Decodable::decode(b)?;
1167        let deposit_nonce = remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?;
1168        let deposit_nonce_version =
1169            remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?;
1170
1171        let this = Self {
1172            inner: ReceiptWithBloom {
1173                receipt: Receipt { status, cumulative_gas_used, logs },
1174                logs_bloom,
1175            },
1176            deposit_nonce,
1177            deposit_receipt_version: deposit_nonce_version,
1178        };
1179
1180        let consumed = started_len - b.len();
1181        if consumed != rlp_head.payload_length {
1182            return Err(alloy_rlp::Error::ListLengthMismatch {
1183                expected: rlp_head.payload_length,
1184                got: consumed,
1185            });
1186        }
1187
1188        *buf = *b;
1189        Ok(this)
1190    }
1191}
1192
1193impl alloy_rlp::Encodable for DepositReceipt {
1194    fn encode(&self, out: &mut dyn BufMut) {
1195        self.encode_fields(out);
1196    }
1197
1198    fn length(&self) -> usize {
1199        let payload_length = self.payload_len();
1200        payload_length + length_of_length(payload_length)
1201    }
1202}
1203
1204impl alloy_rlp::Decodable for DepositReceipt {
1205    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1206        Self::decode_receipt(buf)
1207    }
1208}
1209
1210#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1211#[serde(tag = "type")]
1212pub enum TypedReceipt<T = Receipt<alloy_primitives::Log>> {
1213    #[serde(rename = "0x0", alias = "0x00")]
1214    Legacy(ReceiptWithBloom<T>),
1215    #[serde(rename = "0x1", alias = "0x01")]
1216    EIP2930(ReceiptWithBloom<T>),
1217    #[serde(rename = "0x2", alias = "0x02")]
1218    EIP1559(ReceiptWithBloom<T>),
1219    #[serde(rename = "0x3", alias = "0x03")]
1220    EIP4844(ReceiptWithBloom<T>),
1221    #[serde(rename = "0x4", alias = "0x04")]
1222    EIP7702(ReceiptWithBloom<T>),
1223    #[serde(rename = "0x7E", alias = "0x7e")]
1224    Deposit(DepositReceipt<T>),
1225}
1226
1227impl<T> TypedReceipt<T> {
1228    pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom<T> {
1229        match self {
1230            Self::Legacy(r) |
1231            Self::EIP1559(r) |
1232            Self::EIP2930(r) |
1233            Self::EIP4844(r) |
1234            Self::EIP7702(r) => r,
1235            Self::Deposit(r) => &r.inner,
1236        }
1237    }
1238}
1239
1240impl<T> From<TypedReceipt<T>> for ReceiptWithBloom<T> {
1241    fn from(value: TypedReceipt<T>) -> Self {
1242        match value {
1243            TypedReceipt::Legacy(r) |
1244            TypedReceipt::EIP1559(r) |
1245            TypedReceipt::EIP2930(r) |
1246            TypedReceipt::EIP4844(r) |
1247            TypedReceipt::EIP7702(r) => r,
1248            TypedReceipt::Deposit(r) => r.inner,
1249        }
1250    }
1251}
1252
1253impl From<TypedReceipt<Receipt<alloy_rpc_types::Log>>> for OtsReceipt {
1254    fn from(value: TypedReceipt<Receipt<alloy_rpc_types::Log>>) -> Self {
1255        let r#type = match value {
1256            TypedReceipt::Legacy(_) => 0x00,
1257            TypedReceipt::EIP2930(_) => 0x01,
1258            TypedReceipt::EIP1559(_) => 0x02,
1259            TypedReceipt::EIP4844(_) => 0x03,
1260            TypedReceipt::EIP7702(_) => 0x04,
1261            TypedReceipt::Deposit(_) => 0x7E,
1262        } as u8;
1263        let receipt = ReceiptWithBloom::<Receipt<alloy_rpc_types::Log>>::from(value);
1264        let status = receipt.status();
1265        let cumulative_gas_used = receipt.cumulative_gas_used();
1266        let logs = receipt.logs().to_vec();
1267        let logs_bloom = receipt.logs_bloom;
1268
1269        Self { status, cumulative_gas_used, logs: Some(logs), logs_bloom: Some(logs_bloom), r#type }
1270    }
1271}
1272
1273impl TypedReceipt {
1274    pub fn cumulative_gas_used(&self) -> u64 {
1275        self.as_receipt_with_bloom().cumulative_gas_used()
1276    }
1277
1278    pub fn logs_bloom(&self) -> &Bloom {
1279        &self.as_receipt_with_bloom().logs_bloom
1280    }
1281
1282    pub fn logs(&self) -> &[Log] {
1283        self.as_receipt_with_bloom().logs()
1284    }
1285}
1286
1287impl From<ReceiptEnvelope<alloy_rpc_types::Log>> for TypedReceipt<Receipt<alloy_rpc_types::Log>> {
1288    fn from(value: ReceiptEnvelope<alloy_rpc_types::Log>) -> Self {
1289        match value {
1290            ReceiptEnvelope::Legacy(r) => Self::Legacy(r),
1291            ReceiptEnvelope::Eip2930(r) => Self::EIP2930(r),
1292            ReceiptEnvelope::Eip1559(r) => Self::EIP1559(r),
1293            ReceiptEnvelope::Eip4844(r) => Self::EIP4844(r),
1294            _ => unreachable!(),
1295        }
1296    }
1297}
1298
1299impl Encodable for TypedReceipt {
1300    fn encode(&self, out: &mut dyn bytes::BufMut) {
1301        match self {
1302            Self::Legacy(r) => r.encode(out),
1303            receipt => {
1304                let payload_len = match receipt {
1305                    Self::EIP2930(r) => r.length() + 1,
1306                    Self::EIP1559(r) => r.length() + 1,
1307                    Self::EIP4844(r) => r.length() + 1,
1308                    Self::Deposit(r) => r.length() + 1,
1309                    _ => unreachable!("receipt already matched"),
1310                };
1311
1312                match receipt {
1313                    Self::EIP2930(r) => {
1314                        Header { list: true, payload_length: payload_len }.encode(out);
1315                        1u8.encode(out);
1316                        r.encode(out);
1317                    }
1318                    Self::EIP1559(r) => {
1319                        Header { list: true, payload_length: payload_len }.encode(out);
1320                        2u8.encode(out);
1321                        r.encode(out);
1322                    }
1323                    Self::EIP4844(r) => {
1324                        Header { list: true, payload_length: payload_len }.encode(out);
1325                        3u8.encode(out);
1326                        r.encode(out);
1327                    }
1328                    Self::Deposit(r) => {
1329                        Header { list: true, payload_length: payload_len }.encode(out);
1330                        0x7Eu8.encode(out);
1331                        r.encode(out);
1332                    }
1333                    _ => unreachable!("receipt already matched"),
1334                }
1335            }
1336        }
1337    }
1338}
1339
1340impl Decodable for TypedReceipt {
1341    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1342        use bytes::Buf;
1343        use std::cmp::Ordering;
1344
1345        // a receipt is either encoded as a string (non legacy) or a list (legacy).
1346        // We should not consume the buffer if we are decoding a legacy receipt, so let's
1347        // check if the first byte is between 0x80 and 0xbf.
1348        let rlp_type = *buf
1349            .first()
1350            .ok_or(alloy_rlp::Error::Custom("cannot decode a receipt from empty bytes"))?;
1351
1352        match rlp_type.cmp(&alloy_rlp::EMPTY_LIST_CODE) {
1353            Ordering::Less => {
1354                // strip out the string header
1355                let _header = Header::decode(buf)?;
1356                let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom(
1357                    "typed receipt cannot be decoded from an empty slice",
1358                ))?;
1359                if receipt_type == 0x01 {
1360                    buf.advance(1);
1361                    <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP2930)
1362                } else if receipt_type == 0x02 {
1363                    buf.advance(1);
1364                    <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP1559)
1365                } else if receipt_type == 0x03 {
1366                    buf.advance(1);
1367                    <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::EIP4844)
1368                } else if receipt_type == 0x7E {
1369                    buf.advance(1);
1370                    <DepositReceipt as Decodable>::decode(buf).map(TypedReceipt::Deposit)
1371                } else {
1372                    Err(alloy_rlp::Error::Custom("invalid receipt type"))
1373                }
1374            }
1375            Ordering::Equal => {
1376                Err(alloy_rlp::Error::Custom("an empty list is not a valid receipt encoding"))
1377            }
1378            Ordering::Greater => {
1379                <ReceiptWithBloom as Decodable>::decode(buf).map(TypedReceipt::Legacy)
1380            }
1381        }
1382    }
1383}
1384
1385impl Typed2718 for TypedReceipt {
1386    fn ty(&self) -> u8 {
1387        match self {
1388            Self::Legacy(_) => alloy_consensus::constants::LEGACY_TX_TYPE_ID,
1389            Self::EIP2930(_) => alloy_consensus::constants::EIP2930_TX_TYPE_ID,
1390            Self::EIP1559(_) => alloy_consensus::constants::EIP1559_TX_TYPE_ID,
1391            Self::EIP4844(_) => alloy_consensus::constants::EIP4844_TX_TYPE_ID,
1392            Self::EIP7702(_) => alloy_consensus::constants::EIP7702_TX_TYPE_ID,
1393            Self::Deposit(_) => DEPOSIT_TX_TYPE_ID,
1394        }
1395    }
1396}
1397
1398impl Encodable2718 for TypedReceipt {
1399    fn encode_2718_len(&self) -> usize {
1400        match self {
1401            Self::Legacy(r) => ReceiptEnvelope::Legacy(r.clone()).encode_2718_len(),
1402            Self::EIP2930(r) => ReceiptEnvelope::Eip2930(r.clone()).encode_2718_len(),
1403            Self::EIP1559(r) => ReceiptEnvelope::Eip1559(r.clone()).encode_2718_len(),
1404            Self::EIP4844(r) => ReceiptEnvelope::Eip4844(r.clone()).encode_2718_len(),
1405            Self::EIP7702(r) => 1 + r.length(),
1406            Self::Deposit(r) => 1 + r.length(),
1407        }
1408    }
1409
1410    fn encode_2718(&self, out: &mut dyn BufMut) {
1411        if let Some(ty) = self.type_flag() {
1412            out.put_u8(ty);
1413        }
1414        match self {
1415            Self::Legacy(r) |
1416            Self::EIP2930(r) |
1417            Self::EIP1559(r) |
1418            Self::EIP4844(r) |
1419            Self::EIP7702(r) => r.encode(out),
1420            Self::Deposit(r) => r.encode(out),
1421        }
1422    }
1423}
1424
1425impl Decodable2718 for TypedReceipt {
1426    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1427        if ty == 0x7E {
1428            return Ok(Self::Deposit(DepositReceipt::decode(buf)?));
1429        }
1430        match ReceiptEnvelope::typed_decode(ty, buf)? {
1431            ReceiptEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
1432            ReceiptEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
1433            ReceiptEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
1434            _ => unreachable!(),
1435        }
1436    }
1437
1438    fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
1439        match ReceiptEnvelope::fallback_decode(buf)? {
1440            ReceiptEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
1441            _ => unreachable!(),
1442        }
1443    }
1444}
1445
1446pub type ReceiptResponse = TransactionReceipt<TypedReceipt<Receipt<alloy_rpc_types::Log>>>;
1447
1448pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option<ReceiptResponse> {
1449    let WithOtherFields {
1450        inner:
1451            TransactionReceipt {
1452                transaction_hash,
1453                transaction_index,
1454                block_hash,
1455                block_number,
1456                gas_used,
1457                contract_address,
1458                effective_gas_price,
1459                from,
1460                to,
1461                blob_gas_price,
1462                blob_gas_used,
1463                inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type },
1464            },
1465        other,
1466    } = receipt;
1467
1468    Some(TransactionReceipt {
1469        transaction_hash,
1470        transaction_index,
1471        block_hash,
1472        block_number,
1473        gas_used,
1474        contract_address,
1475        effective_gas_price,
1476        from,
1477        to,
1478        blob_gas_price,
1479        blob_gas_used,
1480        inner: match r#type {
1481            0x00 => TypedReceipt::Legacy(receipt_with_bloom),
1482            0x01 => TypedReceipt::EIP2930(receipt_with_bloom),
1483            0x02 => TypedReceipt::EIP1559(receipt_with_bloom),
1484            0x03 => TypedReceipt::EIP4844(receipt_with_bloom),
1485            0x7E => TypedReceipt::Deposit(DepositReceipt {
1486                inner: receipt_with_bloom,
1487                deposit_nonce: other
1488                    .get_deserialized::<U64>("depositNonce")
1489                    .transpose()
1490                    .ok()?
1491                    .map(|v| v.to()),
1492                deposit_receipt_version: other
1493                    .get_deserialized::<U64>("depositReceiptVersion")
1494                    .transpose()
1495                    .ok()?
1496                    .map(|v| v.to()),
1497            }),
1498            _ => return None,
1499        },
1500    })
1501}
1502
1503#[cfg(test)]
1504mod tests {
1505    use alloy_primitives::{b256, hex, LogData};
1506    use std::str::FromStr;
1507
1508    use super::*;
1509
1510    #[test]
1511    fn test_decode_call() {
1512        let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..];
1513        let decoded = TypedTransaction::decode(&mut &bytes_first[..]).unwrap();
1514
1515        let tx = TxLegacy {
1516            nonce: 2u64,
1517            gas_price: 1000000000u128,
1518            gas_limit: 100000,
1519            to: TxKind::Call(Address::from_slice(
1520                &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..],
1521            )),
1522            value: U256::from(1000000000000000u64),
1523            input: Bytes::default(),
1524            chain_id: Some(4),
1525        };
1526
1527        let signature = PrimitiveSignature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap();
1528
1529        let tx = TypedTransaction::Legacy(Signed::new_unchecked(
1530            tx,
1531            signature,
1532            b256!("0xa517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34"),
1533        ));
1534
1535        assert_eq!(tx, decoded);
1536    }
1537
1538    #[test]
1539    fn test_decode_create_goerli() {
1540        // test that an example create tx from goerli decodes properly
1541        let tx_bytes =
1542              hex::decode("02f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471")
1543                  .unwrap();
1544        let _decoded = TypedTransaction::decode(&mut &tx_bytes[..]).unwrap();
1545    }
1546
1547    #[test]
1548    fn can_recover_sender() {
1549        // random mainnet tx: https://etherscan.io/tx/0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f
1550        let bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap();
1551
1552        let Ok(TypedTransaction::EIP1559(tx)) = TypedTransaction::decode(&mut &bytes[..]) else {
1553            panic!("decoding TypedTransaction failed");
1554        };
1555
1556        assert_eq!(
1557            tx.hash(),
1558            &"0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f"
1559                .parse::<B256>()
1560                .unwrap()
1561        );
1562        assert_eq!(
1563            tx.recover_signer().unwrap(),
1564            "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse::<Address>().unwrap()
1565        );
1566    }
1567
1568    // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
1569    // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
1570    #[test]
1571    fn test_decode_live_4844_tx() {
1572        use alloy_primitives::{address, b256};
1573
1574        // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
1575        let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
1576        let res = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap();
1577        assert_eq!(res.r#type(), Some(3));
1578
1579        let tx = match res {
1580            TypedTransaction::EIP4844(tx) => tx,
1581            _ => unreachable!(),
1582        };
1583
1584        assert_eq!(tx.tx().tx().to, address!("0x11E9CA82A3a762b4B5bd264d4173a242e7a77064"));
1585
1586        assert_eq!(
1587            tx.tx().tx().blob_versioned_hashes,
1588            vec![
1589                b256!("0x012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
1590                b256!("0x0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
1591                b256!("0x013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
1592                b256!("0x01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
1593                b256!("0x011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
1594            ]
1595        );
1596
1597        let from = tx.recover_signer().unwrap();
1598        assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
1599    }
1600
1601    #[test]
1602    fn test_decode_encode_deposit_tx() {
1603        // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7
1604        let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7"
1605            .parse::<TxHash>()
1606            .unwrap();
1607
1608        // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7
1609        let raw_tx = alloy_primitives::hex::decode(
1610            "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080",
1611        )
1612        .unwrap();
1613        let dep_tx = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap();
1614
1615        let mut encoded = Vec::new();
1616        dep_tx.encode_2718(&mut encoded);
1617
1618        assert_eq!(raw_tx, encoded);
1619
1620        assert_eq!(tx_hash, dep_tx.hash());
1621    }
1622
1623    #[test]
1624    fn can_recover_sender_not_normalized() {
1625        let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap();
1626
1627        let Ok(TypedTransaction::Legacy(tx)) = TypedTransaction::decode(&mut &bytes[..]) else {
1628            panic!("decoding TypedTransaction failed");
1629        };
1630
1631        assert_eq!(tx.tx().input, Bytes::from(b""));
1632        assert_eq!(tx.tx().gas_price, 1);
1633        assert_eq!(tx.tx().gas_limit, 21000);
1634        assert_eq!(tx.tx().nonce, 0);
1635        if let TxKind::Call(to) = tx.tx().to {
1636            assert_eq!(
1637                to,
1638                "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::<Address>().unwrap()
1639            );
1640        } else {
1641            panic!("expected a call transaction");
1642        }
1643        assert_eq!(tx.tx().value, U256::from(0x0au64));
1644        assert_eq!(
1645            tx.recover_signer().unwrap(),
1646            "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse::<Address>().unwrap()
1647        );
1648    }
1649
1650    #[test]
1651    fn encode_legacy_receipt() {
1652        let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
1653
1654        let mut data = vec![];
1655        let receipt = TypedReceipt::Legacy(ReceiptWithBloom {
1656            receipt: Receipt {
1657                status: false.into(),
1658                cumulative_gas_used: 0x1,
1659                logs: vec![Log {
1660                    address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
1661                    data: LogData::new_unchecked(
1662                        vec![
1663                            B256::from_str(
1664                                "000000000000000000000000000000000000000000000000000000000000dead",
1665                            )
1666                            .unwrap(),
1667                            B256::from_str(
1668                                "000000000000000000000000000000000000000000000000000000000000beef",
1669                            )
1670                            .unwrap(),
1671                        ],
1672                        Bytes::from_str("0100ff").unwrap(),
1673                    ),
1674                }],
1675            },
1676            logs_bloom: [0; 256].into(),
1677        });
1678
1679        receipt.encode(&mut data);
1680
1681        // check that the rlp length equals the length of the expected rlp
1682        assert_eq!(receipt.length(), expected.len());
1683        assert_eq!(data, expected);
1684    }
1685
1686    #[test]
1687    fn decode_legacy_receipt() {
1688        let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
1689
1690        let expected = TypedReceipt::Legacy(ReceiptWithBloom {
1691            receipt: Receipt {
1692                status: false.into(),
1693                cumulative_gas_used: 0x1,
1694                logs: vec![Log {
1695                    address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
1696                    data: LogData::new_unchecked(
1697                        vec![
1698                            B256::from_str(
1699                                "000000000000000000000000000000000000000000000000000000000000dead",
1700                            )
1701                            .unwrap(),
1702                            B256::from_str(
1703                                "000000000000000000000000000000000000000000000000000000000000beef",
1704                            )
1705                            .unwrap(),
1706                        ],
1707                        Bytes::from_str("0100ff").unwrap(),
1708                    ),
1709                }],
1710            },
1711            logs_bloom: [0; 256].into(),
1712        });
1713
1714        let receipt = TypedReceipt::decode(&mut &data[..]).unwrap();
1715
1716        assert_eq!(receipt, expected);
1717    }
1718
1719    #[test]
1720    fn deser_to_type_tx() {
1721        let tx = r#"
1722        {
1723            "EIP1559": { 
1724                "chainId": "0x7a69",
1725                "nonce": "0x0",
1726                "gas": "0x5209",
1727                "maxFeePerGas": "0x77359401",
1728                "maxPriorityFeePerGas": "0x1",
1729                "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
1730                "value": "0x0",
1731                "accessList": [],
1732                "input": "0x",
1733                "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
1734                "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
1735                "yParity": "0x0",
1736                "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
1737            }
1738        }"#;
1739
1740        let _typed_tx: TypedTransaction = serde_json::from_str(tx).unwrap();
1741    }
1742}