anvil_core/eth/transaction/
mod.rs

1//! Transaction related types
2use alloy_consensus::{
3    Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, Transaction, TxEip1559, TxEip2930,
4    TxEnvelope, TxLegacy, TxReceipt, Typed2718,
5    transaction::{
6        Recovered, TxEip7702,
7        eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar},
8    },
9};
10
11use alloy_eips::eip2718::Encodable2718;
12use alloy_network::{AnyReceiptEnvelope, AnyTransactionReceipt};
13use alloy_primitives::{Address, B256, Bloom, Bytes, TxHash, TxKind, U64, U256};
14use alloy_rlp::{Decodable, Encodable};
15use alloy_rpc_types::{
16    Transaction as RpcTransaction, TransactionReceipt, request::TransactionRequest,
17    trace::otterscan::OtsReceipt,
18};
19use alloy_serde::{OtherFields, WithOtherFields};
20use bytes::BufMut;
21use foundry_evm::traces::CallTraceNode;
22use foundry_primitives::{FoundryTxEnvelope, FoundryTypedTx};
23use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom, TxDeposit};
24use revm::interpreter::InstructionResult;
25use serde::{Deserialize, Serialize};
26use std::ops::Deref;
27
28/// Converts a [TransactionRequest] into a [FoundryTypedTx].
29/// Should be removed once the call builder abstraction for providers is in place.
30pub fn transaction_request_to_typed(
31    tx: WithOtherFields<TransactionRequest>,
32) -> Option<FoundryTypedTx> {
33    let WithOtherFields::<TransactionRequest> {
34        inner:
35            TransactionRequest {
36                from,
37                to,
38                gas_price,
39                max_fee_per_gas,
40                max_priority_fee_per_gas,
41                max_fee_per_blob_gas,
42                blob_versioned_hashes,
43                gas,
44                value,
45                input,
46                nonce,
47                access_list,
48                sidecar,
49                transaction_type,
50                authorization_list,
51                chain_id: _,
52            },
53        other,
54    } = tx;
55
56    // Special case: OP-stack deposit tx
57    if transaction_type == Some(0x7E) || has_optimism_fields(&other) {
58        let mint = other.get_deserialized::<U256>("mint")?.map(|m| m.to::<u128>()).ok()?;
59
60        return Some(FoundryTypedTx::Deposit(TxDeposit {
61            from: from.unwrap_or_default(),
62            source_hash: other.get_deserialized::<B256>("sourceHash")?.ok()?,
63            to: to.unwrap_or_default(),
64            mint,
65            value: value.unwrap_or_default(),
66            gas_limit: gas.unwrap_or_default(),
67            is_system_transaction: other.get_deserialized::<bool>("isSystemTx")?.ok()?,
68            input: input.into_input().unwrap_or_default(),
69        }));
70    }
71
72    // EIP7702
73    if transaction_type == Some(4) || authorization_list.is_some() {
74        return Some(FoundryTypedTx::Eip7702(TxEip7702 {
75            nonce: nonce.unwrap_or_default(),
76            max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
77            max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
78            gas_limit: gas.unwrap_or_default(),
79            value: value.unwrap_or(U256::ZERO),
80            input: input.into_input().unwrap_or_default(),
81            // requires to
82            to: to?.into_to()?,
83            chain_id: 0,
84            access_list: access_list.unwrap_or_default(),
85            authorization_list: authorization_list.unwrap_or_default(),
86        }));
87    }
88
89    match (
90        transaction_type,
91        gas_price,
92        max_fee_per_gas,
93        max_priority_fee_per_gas,
94        access_list.as_ref(),
95        max_fee_per_blob_gas,
96        blob_versioned_hashes.as_ref(),
97        sidecar.as_ref(),
98        to,
99    ) {
100        // legacy transaction
101        (Some(0), _, None, None, None, None, None, None, _)
102        | (None, Some(_), None, None, None, None, None, None, _) => {
103            Some(FoundryTypedTx::Legacy(TxLegacy {
104                nonce: nonce.unwrap_or_default(),
105                gas_price: gas_price.unwrap_or_default(),
106                gas_limit: gas.unwrap_or_default(),
107                value: value.unwrap_or(U256::ZERO),
108                input: input.into_input().unwrap_or_default(),
109                to: to.unwrap_or_default(),
110                chain_id: None,
111            }))
112        }
113        // EIP2930
114        (Some(1), _, None, None, _, None, None, None, _)
115        | (None, _, None, None, Some(_), None, None, None, _) => {
116            Some(FoundryTypedTx::Eip2930(TxEip2930 {
117                nonce: nonce.unwrap_or_default(),
118                gas_price: gas_price.unwrap_or_default(),
119                gas_limit: gas.unwrap_or_default(),
120                value: value.unwrap_or(U256::ZERO),
121                input: input.into_input().unwrap_or_default(),
122                to: to.unwrap_or_default(),
123                chain_id: 0,
124                access_list: access_list.unwrap_or_default(),
125            }))
126        }
127        // EIP1559
128        (Some(2), None, _, _, _, _, None, None, _)
129        | (None, None, Some(_), _, _, _, None, None, _)
130        | (None, None, _, Some(_), _, _, None, None, _)
131        | (None, None, None, None, None, _, None, None, _) => {
132            // Empty fields fall back to the canonical transaction schema.
133            Some(FoundryTypedTx::Eip1559(TxEip1559 {
134                nonce: nonce.unwrap_or_default(),
135                max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
136                max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
137                gas_limit: gas.unwrap_or_default(),
138                value: value.unwrap_or(U256::ZERO),
139                input: input.into_input().unwrap_or_default(),
140                to: to.unwrap_or_default(),
141                chain_id: 0,
142                access_list: access_list.unwrap_or_default(),
143            }))
144        }
145        // EIP4844
146        (Some(3), None, _, _, _, _, Some(_), _, to) => {
147            let tx = TxEip4844 {
148                nonce: nonce.unwrap_or_default(),
149                max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
150                max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(),
151                max_fee_per_blob_gas: max_fee_per_blob_gas.unwrap_or_default(),
152                gas_limit: gas.unwrap_or_default(),
153                value: value.unwrap_or(U256::ZERO),
154                input: input.into_input().unwrap_or_default(),
155                to: match to.unwrap_or(TxKind::Create) {
156                    TxKind::Call(to) => to,
157                    TxKind::Create => Address::ZERO,
158                },
159                chain_id: 0,
160                access_list: access_list.unwrap_or_default(),
161                blob_versioned_hashes: blob_versioned_hashes.unwrap_or_default(),
162            };
163
164            if let Some(sidecar) = sidecar {
165                Some(FoundryTypedTx::Eip4844(TxEip4844Variant::TxEip4844WithSidecar(
166                    TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar),
167                )))
168            } else {
169                Some(FoundryTypedTx::Eip4844(TxEip4844Variant::TxEip4844(tx)))
170            }
171        }
172        _ => None,
173    }
174}
175
176pub fn has_optimism_fields(other: &OtherFields) -> bool {
177    other.contains_key("sourceHash")
178        && other.contains_key("mint")
179        && other.contains_key("isSystemTx")
180}
181
182/// A wrapper for [FoundryTxEnvelope] that allows impersonating accounts.
183///
184/// This is a helper that carries the `impersonated` sender so that the right hash
185/// [FoundryTxEnvelope::impersonated_hash] can be created.
186#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
187pub struct MaybeImpersonatedTransaction {
188    transaction: FoundryTxEnvelope,
189    impersonated_sender: Option<Address>,
190}
191
192impl Typed2718 for MaybeImpersonatedTransaction {
193    fn ty(&self) -> u8 {
194        self.transaction.ty()
195    }
196}
197
198impl MaybeImpersonatedTransaction {
199    /// Creates a new wrapper for the given transaction
200    pub fn new(transaction: FoundryTxEnvelope) -> Self {
201        Self { transaction, impersonated_sender: None }
202    }
203
204    /// Creates a new impersonated transaction wrapper using the given sender
205    pub fn impersonated(transaction: FoundryTxEnvelope, impersonated_sender: Address) -> Self {
206        Self { transaction, impersonated_sender: Some(impersonated_sender) }
207    }
208
209    /// Recovers the Ethereum address which was used to sign the transaction.
210    pub fn recover(&self) -> Result<Address, alloy_primitives::SignatureError> {
211        if let Some(sender) = self.impersonated_sender {
212            return Ok(sender);
213        }
214        self.transaction.recover()
215    }
216
217    /// Returns whether the transaction is impersonated
218    pub fn is_impersonated(&self) -> bool {
219        self.impersonated_sender.is_some()
220    }
221
222    /// Returns the hash of the transaction
223    pub fn hash(&self) -> B256 {
224        if let Some(sender) = self.impersonated_sender {
225            return self.transaction.impersonated_hash(sender);
226        }
227        self.transaction.hash()
228    }
229
230    /// Converts the transaction into an [`RpcTransaction`]
231    pub fn into_rpc_transaction(self) -> RpcTransaction {
232        let hash = self.hash();
233        let from = self.recover().unwrap_or_default();
234        let envelope = self.transaction.try_into_eth().expect("cant build deposit transactions");
235
236        // NOTE: we must update the hash because the tx can be impersonated, this requires forcing
237        // the hash
238        let inner_envelope = match envelope {
239            TxEnvelope::Legacy(t) => {
240                let (tx, sig, _) = t.into_parts();
241                TxEnvelope::Legacy(Signed::new_unchecked(tx, sig, hash))
242            }
243            TxEnvelope::Eip2930(t) => {
244                let (tx, sig, _) = t.into_parts();
245                TxEnvelope::Eip2930(Signed::new_unchecked(tx, sig, hash))
246            }
247            TxEnvelope::Eip1559(t) => {
248                let (tx, sig, _) = t.into_parts();
249                TxEnvelope::Eip1559(Signed::new_unchecked(tx, sig, hash))
250            }
251            TxEnvelope::Eip4844(t) => {
252                let (tx, sig, _) = t.into_parts();
253                TxEnvelope::Eip4844(Signed::new_unchecked(tx, sig, hash))
254            }
255            TxEnvelope::Eip7702(t) => {
256                let (tx, sig, _) = t.into_parts();
257                TxEnvelope::Eip7702(Signed::new_unchecked(tx, sig, hash))
258            }
259        };
260
261        RpcTransaction {
262            block_hash: None,
263            block_number: None,
264            transaction_index: None,
265            effective_gas_price: None,
266            inner: Recovered::new_unchecked(inner_envelope, from),
267        }
268    }
269}
270
271impl Encodable2718 for MaybeImpersonatedTransaction {
272    fn encode_2718_len(&self) -> usize {
273        self.transaction.encode_2718_len()
274    }
275
276    fn encode_2718(&self, out: &mut dyn BufMut) {
277        self.transaction.encode_2718(out)
278    }
279}
280
281impl Encodable for MaybeImpersonatedTransaction {
282    fn encode(&self, out: &mut dyn bytes::BufMut) {
283        self.transaction.encode(out)
284    }
285}
286
287impl From<MaybeImpersonatedTransaction> for FoundryTxEnvelope {
288    fn from(value: MaybeImpersonatedTransaction) -> Self {
289        value.transaction
290    }
291}
292
293impl From<FoundryTxEnvelope> for MaybeImpersonatedTransaction {
294    fn from(value: FoundryTxEnvelope) -> Self {
295        Self::new(value)
296    }
297}
298
299impl Decodable for MaybeImpersonatedTransaction {
300    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
301        FoundryTxEnvelope::decode(buf).map(Self::new)
302    }
303}
304
305impl AsRef<FoundryTxEnvelope> for MaybeImpersonatedTransaction {
306    fn as_ref(&self) -> &FoundryTxEnvelope {
307        &self.transaction
308    }
309}
310
311impl Deref for MaybeImpersonatedTransaction {
312    type Target = FoundryTxEnvelope;
313
314    fn deref(&self) -> &Self::Target {
315        &self.transaction
316    }
317}
318
319impl From<MaybeImpersonatedTransaction> for RpcTransaction {
320    fn from(value: MaybeImpersonatedTransaction) -> Self {
321        value.into_rpc_transaction()
322    }
323}
324
325/// Queued transaction
326#[derive(Clone, Debug, PartialEq, Eq)]
327pub struct PendingTransaction {
328    /// The actual transaction
329    pub transaction: MaybeImpersonatedTransaction,
330    /// the recovered sender of this transaction
331    sender: Address,
332    /// hash of `transaction`, so it can easily be reused with encoding and hashing again
333    hash: TxHash,
334}
335
336impl PendingTransaction {
337    pub fn new(transaction: FoundryTxEnvelope) -> Result<Self, alloy_primitives::SignatureError> {
338        let sender = transaction.recover()?;
339        let hash = transaction.hash();
340        Ok(Self { transaction: MaybeImpersonatedTransaction::new(transaction), sender, hash })
341    }
342
343    pub fn with_impersonated(transaction: FoundryTxEnvelope, sender: Address) -> Self {
344        let hash = transaction.impersonated_hash(sender);
345        Self {
346            transaction: MaybeImpersonatedTransaction::impersonated(transaction, sender),
347            sender,
348            hash,
349        }
350    }
351
352    /// Converts a [`MaybeImpersonatedTransaction`] into a [`PendingTransaction`].
353    pub fn from_maybe_impersonated(
354        transaction: MaybeImpersonatedTransaction,
355    ) -> Result<Self, alloy_primitives::SignatureError> {
356        if let Some(impersonated) = transaction.impersonated_sender {
357            Ok(Self::with_impersonated(transaction.transaction, impersonated))
358        } else {
359            Self::new(transaction.transaction)
360        }
361    }
362
363    pub fn nonce(&self) -> u64 {
364        self.transaction.nonce()
365    }
366
367    pub fn hash(&self) -> &TxHash {
368        &self.hash
369    }
370
371    pub fn sender(&self) -> &Address {
372        &self.sender
373    }
374}
375
376/// Represents all relevant information of an executed transaction
377#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
378pub struct TransactionInfo {
379    pub transaction_hash: B256,
380    pub transaction_index: u64,
381    pub from: Address,
382    pub to: Option<Address>,
383    pub contract_address: Option<Address>,
384    pub traces: Vec<CallTraceNode>,
385    pub exit: InstructionResult,
386    pub out: Option<Bytes>,
387    pub nonce: u64,
388    pub gas_used: u64,
389}
390
391/// RPC-specific variant of TypedReceipt for boundary conversion
392#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
393#[serde(tag = "type")]
394pub enum TypedReceiptRpc {
395    #[serde(rename = "0x0", alias = "0x00")]
396    Legacy(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
397    #[serde(rename = "0x1", alias = "0x01")]
398    Eip2930(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
399    #[serde(rename = "0x2", alias = "0x02")]
400    Eip1559(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
401    #[serde(rename = "0x3", alias = "0x03")]
402    Eip4844(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
403    #[serde(rename = "0x4", alias = "0x04")]
404    Eip7702(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
405    #[serde(rename = "0x7E", alias = "0x7e")]
406    Deposit(OpDepositReceiptWithBloom),
407}
408
409impl TypedReceiptRpc {
410    pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom<Receipt<alloy_rpc_types::Log>> {
411        match self {
412            Self::Legacy(r)
413            | Self::Eip1559(r)
414            | Self::Eip2930(r)
415            | Self::Eip4844(r)
416            | Self::Eip7702(r) => r,
417            Self::Deposit(_) => unreachable!("use variant-specific helpers for deposit"),
418        }
419    }
420
421    pub fn logs_bloom(&self) -> &Bloom {
422        match self {
423            Self::Legacy(r)
424            | Self::Eip1559(r)
425            | Self::Eip2930(r)
426            | Self::Eip4844(r)
427            | Self::Eip7702(r) => &r.logs_bloom,
428            Self::Deposit(r) => &r.logs_bloom,
429        }
430    }
431
432    pub fn logs(&self) -> &[alloy_rpc_types::Log] {
433        match self {
434            Self::Legacy(r)
435            | Self::Eip1559(r)
436            | Self::Eip2930(r)
437            | Self::Eip4844(r)
438            | Self::Eip7702(r) => &r.receipt.logs,
439            Self::Deposit(_) => unreachable!("use variant-specific helpers for deposit"),
440        }
441    }
442
443    pub fn cumulative_gas_used(&self) -> u64 {
444        self.as_receipt_with_bloom().cumulative_gas_used()
445    }
446}
447
448// Intentionally only provide a concrete conversion used by RPC response/Otterscan path.
449impl From<TypedReceiptRpc> for ReceiptWithBloom<Receipt<alloy_rpc_types::Log>> {
450    fn from(value: TypedReceiptRpc) -> Self {
451        match value {
452            TypedReceiptRpc::Legacy(r)
453            | TypedReceiptRpc::Eip1559(r)
454            | TypedReceiptRpc::Eip2930(r)
455            | TypedReceiptRpc::Eip4844(r)
456            | TypedReceiptRpc::Eip7702(r) => r,
457            TypedReceiptRpc::Deposit(r) => {
458                // Convert OP deposit receipt (primitives::Log) to RPC receipt (rpc_types::Log)
459                let receipt = Receipt::<alloy_rpc_types::Log> {
460                    status: r.receipt.inner.status,
461                    cumulative_gas_used: r.receipt.inner.cumulative_gas_used,
462                    logs: r
463                        .receipt
464                        .inner
465                        .logs
466                        .into_iter()
467                        .map(|l| alloy_rpc_types::Log {
468                            inner: l,
469                            block_hash: None,
470                            block_number: None,
471                            block_timestamp: None,
472                            transaction_hash: None,
473                            transaction_index: None,
474                            log_index: None,
475                            removed: false,
476                        })
477                        .collect(),
478                };
479                Self { receipt, logs_bloom: r.logs_bloom }
480            }
481        }
482    }
483}
484
485impl From<TypedReceiptRpc> for OtsReceipt {
486    fn from(value: TypedReceiptRpc) -> Self {
487        let r#type = match value {
488            TypedReceiptRpc::Legacy(_) => 0x00,
489            TypedReceiptRpc::Eip2930(_) => 0x01,
490            TypedReceiptRpc::Eip1559(_) => 0x02,
491            TypedReceiptRpc::Eip4844(_) => 0x03,
492            TypedReceiptRpc::Eip7702(_) => 0x04,
493            TypedReceiptRpc::Deposit(_) => 0x7E,
494        } as u8;
495        let receipt = ReceiptWithBloom::<Receipt<alloy_rpc_types::Log>>::from(value);
496        let status = receipt.status();
497        let cumulative_gas_used = receipt.cumulative_gas_used();
498        let logs = receipt.logs().to_vec();
499        let logs_bloom = receipt.logs_bloom;
500
501        Self { status, cumulative_gas_used, logs: Some(logs), logs_bloom: Some(logs_bloom), r#type }
502    }
503}
504
505impl From<ReceiptEnvelope<alloy_rpc_types::Log>> for TypedReceiptRpc {
506    fn from(value: ReceiptEnvelope<alloy_rpc_types::Log>) -> Self {
507        match value {
508            ReceiptEnvelope::Legacy(r) => Self::Legacy(r),
509            ReceiptEnvelope::Eip2930(r) => Self::Eip2930(r),
510            ReceiptEnvelope::Eip1559(r) => Self::Eip1559(r),
511            ReceiptEnvelope::Eip4844(r) => Self::Eip4844(r),
512            ReceiptEnvelope::Eip7702(r) => Self::Eip7702(r),
513        }
514    }
515}
516
517pub type ReceiptResponse = WithOtherFields<TransactionReceipt<TypedReceiptRpc>>;
518
519pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option<ReceiptResponse> {
520    let WithOtherFields {
521        inner:
522            TransactionReceipt {
523                transaction_hash,
524                transaction_index,
525                block_hash,
526                block_number,
527                gas_used,
528                contract_address,
529                effective_gas_price,
530                from,
531                to,
532                blob_gas_price,
533                blob_gas_used,
534                inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type },
535            },
536        other,
537    } = receipt;
538
539    Some(WithOtherFields {
540        inner: TransactionReceipt {
541            transaction_hash,
542            transaction_index,
543            block_hash,
544            block_number,
545            gas_used,
546            contract_address,
547            effective_gas_price,
548            from,
549            to,
550            blob_gas_price,
551            blob_gas_used,
552            inner: match r#type {
553                0x00 => TypedReceiptRpc::Legacy(receipt_with_bloom),
554                0x01 => TypedReceiptRpc::Eip2930(receipt_with_bloom),
555                0x02 => TypedReceiptRpc::Eip1559(receipt_with_bloom),
556                0x03 => TypedReceiptRpc::Eip4844(receipt_with_bloom),
557                0x04 => TypedReceiptRpc::Eip7702(receipt_with_bloom),
558                0x7E => TypedReceiptRpc::Deposit(OpDepositReceiptWithBloom {
559                    receipt: OpDepositReceipt {
560                        inner: Receipt {
561                            status: alloy_consensus::Eip658Value::Eip658(
562                                receipt_with_bloom.status(),
563                            ),
564                            cumulative_gas_used: receipt_with_bloom.cumulative_gas_used(),
565                            logs: receipt_with_bloom
566                                .receipt
567                                .logs
568                                .into_iter()
569                                .map(|l| l.inner)
570                                .collect(),
571                        },
572                        deposit_nonce: other
573                            .get_deserialized::<U64>("depositNonce")
574                            .transpose()
575                            .ok()?
576                            .map(|v| v.to()),
577                        deposit_receipt_version: other
578                            .get_deserialized::<U64>("depositReceiptVersion")
579                            .transpose()
580                            .ok()?
581                            .map(|v| v.to()),
582                    },
583                    logs_bloom: receipt_with_bloom.logs_bloom,
584                }),
585                _ => return None,
586            },
587        },
588        other,
589    })
590}
591
592#[cfg(test)]
593mod tests {
594    use super::*;
595
596    // <https://github.com/foundry-rs/foundry/issues/10852>
597    #[test]
598    fn test_receipt_convert() {
599        let s = r#"{"type":"0x4","status":"0x1","cumulativeGasUsed":"0x903fd1","logs":[{"address":"0x0000d9fcd47bf761e7287d8ee09917d7e2100000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000234ce51365b9c417171b6dad280f49143e1b0547"],"data":"0x00000000000000000000000000000000000000000000032139b42c3431700000","blockHash":"0xd26b59c1d8b5bfa9362d19eb0da3819dfe0b367987a71f6d30908dd45e0d7a60","blockNumber":"0x159663e","blockTimestamp":"0x68411f7b","transactionHash":"0x17a6af73d1317e69cfc3cac9221bd98261d40f24815850a44dbfbf96652ae52a","transactionIndex":"0x22","logIndex":"0x158","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000008100000000000000000000000000000000000000000000000020000200000000000000800000000800000000000000010000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x17a6af73d1317e69cfc3cac9221bd98261d40f24815850a44dbfbf96652ae52a","transactionIndex":"0x22","blockHash":"0xd26b59c1d8b5bfa9362d19eb0da3819dfe0b367987a71f6d30908dd45e0d7a60","blockNumber":"0x159663e","gasUsed":"0x28ee7","effectiveGasPrice":"0x4bf02090","from":"0x234ce51365b9c417171b6dad280f49143e1b0547","to":"0x234ce51365b9c417171b6dad280f49143e1b0547","contractAddress":null}"#;
600        let receipt: AnyTransactionReceipt = serde_json::from_str(s).unwrap();
601        let _converted = convert_to_anvil_receipt(receipt).unwrap();
602    }
603}