Skip to main content

foundry_common_fmt/
ui.rs

1//! Helper trait and functions to format Ethereum types.
2
3use alloy_consensus::{
4    BlockHeader, Eip658Value, Signed, Transaction as TxTrait, TxEip1559, TxEip2930,
5    TxEip4844Variant, TxEip7702, TxEnvelope, TxLegacy, TxReceipt, Typed2718,
6    transaction::TxHashRef,
7};
8use alloy_network::{
9    AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope,
10    BlockResponse, Network, ReceiptResponse, primitives::HeaderResponse,
11};
12use alloy_primitives::{
13    Address, Bloom, Bytes, FixedBytes, I256, Signature, U8, U64, U256, Uint, hex,
14};
15use alloy_rpc_types::{
16    AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt,
17};
18use alloy_serde::{OtherFields, WithOtherFields};
19use revm::context_interface::transaction::SignedAuthorization;
20use serde::{Deserialize, Serialize};
21use std::num::NonZeroU64;
22use tempo_alloy::{
23    primitives::{
24        AASigned, TempoSignature, TempoTransaction, TempoTxEnvelope,
25        transaction::{Call, PrimitiveSignature},
26    },
27    rpc::{TempoHeaderResponse, TempoTransactionReceipt},
28};
29
30#[cfg(feature = "base")]
31use base_common_consensus::{
32    BaseReceipt, BaseTxEnvelope, Eip8130Signed, TxDeposit as BaseTxDeposit,
33};
34#[cfg(feature = "base")]
35use base_common_rpc_types::{BaseTransactionReceipt, Transaction as BaseRpcTransaction};
36
37#[cfg(feature = "optimism")]
38use op_alloy_consensus::{OpTxEnvelope, TxDeposit, TxPostExec};
39
40/// length of the name column for pretty formatting `{:>20}{value}`
41const NAME_COLUMN_LEN: usize = 20usize;
42
43/// Helper trait to format Ethereum types.
44///
45/// # Examples
46///
47/// ```
48/// use foundry_common_fmt::UIfmt;
49///
50/// let boolean: bool = true;
51/// let string = boolean.pretty();
52/// ```
53pub trait UIfmt {
54    /// Return a prettified string version of the value
55    fn pretty(&self) -> String;
56}
57
58impl<T: UIfmt> UIfmt for &T {
59    fn pretty(&self) -> String {
60        (*self).pretty()
61    }
62}
63
64impl<T: UIfmt> UIfmt for Option<T> {
65    fn pretty(&self) -> String {
66        if let Some(inner) = self { inner.pretty() } else { String::new() }
67    }
68}
69
70impl<T: UIfmt> UIfmt for [T] {
71    fn pretty(&self) -> String {
72        if self.is_empty() {
73            "[]".to_string()
74        } else {
75            let mut s = String::with_capacity(self.len() * 64);
76            s.push_str("[\n");
77            for item in self {
78                for line in item.pretty().lines() {
79                    s.push('\t');
80                    s.push_str(line);
81                    s.push('\n');
82                }
83            }
84            s.push(']');
85            s
86        }
87    }
88}
89
90impl UIfmt for String {
91    fn pretty(&self) -> String {
92        self.clone()
93    }
94}
95
96impl UIfmt for u64 {
97    fn pretty(&self) -> String {
98        self.to_string()
99    }
100}
101
102impl UIfmt for NonZeroU64 {
103    fn pretty(&self) -> String {
104        self.get().pretty()
105    }
106}
107
108impl UIfmt for u128 {
109    fn pretty(&self) -> String {
110        self.to_string()
111    }
112}
113
114impl UIfmt for bool {
115    fn pretty(&self) -> String {
116        self.to_string()
117    }
118}
119
120impl<const BITS: usize, const LIMBS: usize> UIfmt for Uint<BITS, LIMBS> {
121    fn pretty(&self) -> String {
122        self.to_string()
123    }
124}
125
126impl UIfmt for I256 {
127    fn pretty(&self) -> String {
128        self.to_string()
129    }
130}
131
132impl UIfmt for Address {
133    fn pretty(&self) -> String {
134        self.to_string()
135    }
136}
137
138impl UIfmt for Bloom {
139    fn pretty(&self) -> String {
140        self.to_string()
141    }
142}
143
144impl UIfmt for Vec<u8> {
145    fn pretty(&self) -> String {
146        self[..].pretty()
147    }
148}
149
150impl UIfmt for Bytes {
151    fn pretty(&self) -> String {
152        self[..].pretty()
153    }
154}
155
156impl<const N: usize> UIfmt for [u8; N] {
157    fn pretty(&self) -> String {
158        self[..].pretty()
159    }
160}
161
162impl<const N: usize> UIfmt for FixedBytes<N> {
163    fn pretty(&self) -> String {
164        self[..].pretty()
165    }
166}
167
168impl UIfmt for [u8] {
169    fn pretty(&self) -> String {
170        hex::encode_prefixed(self)
171    }
172}
173
174impl UIfmt for Eip658Value {
175    fn pretty(&self) -> String {
176        match self {
177            Self::Eip658(status) => if *status { "1 (success)" } else { "0 (failed)" }.to_string(),
178            Self::PostState(state) => state.pretty(),
179        }
180    }
181}
182
183impl UIfmt for Signature {
184    fn pretty(&self) -> String {
185        format!("[r: {}, s: {}, y_parity: {}]", self.r(), self.s(), self.v())
186    }
187}
188
189/// Pretty-prints the common fields of any `TransactionReceipt<T>`.
190fn pretty_receipt<T: TxReceipt>(receipt: &TransactionReceipt<T>, tx_type: u8) -> String
191where
192    T::Log: Serialize,
193{
194    let mut pretty = format!(
195        "
196blockHash            {}
197blockNumber          {}
198contractAddress      {}
199cumulativeGasUsed    {}
200effectiveGasPrice    {}
201from                 {}
202gasUsed              {}
203logs                 {}
204logsBloom            {}
205root                 {}
206status               {}
207transactionHash      {}
208transactionIndex     {}
209type                 {}
210blobGasPrice         {}
211blobGasUsed          {}",
212        receipt.block_hash.pretty(),
213        receipt.block_number.pretty(),
214        receipt.contract_address.pretty(),
215        receipt.inner.cumulative_gas_used().pretty(),
216        receipt.effective_gas_price.pretty(),
217        receipt.from.pretty(),
218        receipt.gas_used.pretty(),
219        serde_json::to_string(receipt.inner.logs()).unwrap(),
220        receipt.inner.bloom().pretty(),
221        receipt.inner.status_or_post_state().as_post_state().pretty(),
222        receipt.inner.status_or_post_state().pretty(),
223        receipt.transaction_hash.pretty(),
224        receipt.transaction_index.pretty(),
225        tx_type,
226        receipt.blob_gas_price.pretty(),
227        receipt.blob_gas_used.pretty()
228    );
229
230    if let Some(to) = receipt.to {
231        pretty.push_str(&format!("\nto                   {}", to.pretty()));
232    }
233
234    pretty
235}
236
237impl UIfmt for TransactionReceipt {
238    fn pretty(&self) -> String {
239        pretty_receipt(self, self.transaction_type() as u8)
240    }
241}
242
243#[cfg(feature = "base")]
244impl UIfmt for BaseTransactionReceipt {
245    fn pretty(&self) -> String {
246        let (deposit_nonce, deposit_receipt_version) = match &self.inner.inner.receipt {
247            BaseReceipt::Deposit(receipt) => {
248                (receipt.deposit_nonce, receipt.deposit_receipt_version)
249            }
250            _ => (None, None),
251        };
252        format!(
253            "{}
254l1GasPrice           {}
255l1GasUsed            {}
256l1Fee                {}
257l1FeeScalar          {}
258l1BaseFeeScalar      {}
259l1BlobBaseFee        {}
260l1BlobBaseFeeScalar  {}
261operatorFeeScalar    {}
262operatorFeeConstant  {}
263daFootprintGasScalar {}
264depositNonce         {}
265depositReceiptVersion {}
266payer                {}
267phaseStatuses        {}
268metadata             {}",
269            pretty_receipt(&self.inner, self.inner.inner.receipt.tx_type() as u8).trim_end(),
270            self.l1_block_info.l1_gas_price.pretty(),
271            self.l1_block_info.l1_gas_used.pretty(),
272            self.l1_block_info.l1_fee.pretty(),
273            self.l1_block_info.l1_fee_scalar.map(|value| value.to_string()).unwrap_or_default(),
274            self.l1_block_info.l1_base_fee_scalar.pretty(),
275            self.l1_block_info.l1_blob_base_fee.pretty(),
276            self.l1_block_info.l1_blob_base_fee_scalar.pretty(),
277            self.l1_block_info.operator_fee_scalar.pretty(),
278            self.l1_block_info.operator_fee_constant.pretty(),
279            self.l1_block_info
280                .da_footprint_gas_scalar
281                .map(|value| value.to_string())
282                .unwrap_or_default(),
283            deposit_nonce.pretty(),
284            deposit_receipt_version.pretty(),
285            self.payer.pretty(),
286            self.phase_statuses.pretty(),
287            self.metadata.pretty(),
288        )
289    }
290}
291
292impl UIfmt for AnyTransactionReceipt {
293    fn pretty(&self) -> String {
294        let mut pretty = pretty_receipt(&self.inner, self.inner.inner.r#type);
295        pretty.push_str(&self.other.pretty());
296        pretty
297    }
298}
299
300impl UIfmt for Log {
301    fn pretty(&self) -> String {
302        format!(
303            "
304address: {}
305blockHash: {}
306blockNumber: {}
307data: {}
308logIndex: {}
309removed: {}
310topics: {}
311transactionHash: {}
312transactionIndex: {}",
313            self.address().pretty(),
314            self.block_hash.pretty(),
315            self.block_number.pretty(),
316            self.data().data.pretty(),
317            self.log_index.pretty(),
318            self.removed.pretty(),
319            self.topics().pretty(),
320            self.transaction_hash.pretty(),
321            self.transaction_index.pretty(),
322        )
323    }
324}
325
326impl<T: UIfmt, H: HeaderResponse + UIfmtHeaderExt> UIfmt for Block<T, H> {
327    fn pretty(&self) -> String {
328        format!(
329            "
330{}
331transactions:        {}",
332            pretty_generic_header_response(&self.header),
333            self.transactions.pretty()
334        )
335    }
336}
337
338impl<T: UIfmt> UIfmt for BlockTransactions<T> {
339    fn pretty(&self) -> String {
340        match self {
341            Self::Hashes(hashes) => hashes.pretty(),
342            Self::Full(transactions) => transactions.pretty(),
343            Self::Uncle => String::new(),
344        }
345    }
346}
347
348impl UIfmt for OtherFields {
349    fn pretty(&self) -> String {
350        let mut s = String::with_capacity(self.len() * 30);
351        if !self.is_empty() {
352            s.push('\n');
353        }
354        for (key, value) in self {
355            let val = EthValue::from(value.clone()).pretty();
356            let offset = NAME_COLUMN_LEN.saturating_sub(key.len());
357            s.push_str(key);
358            s.extend(std::iter::repeat_n(' ', offset + 1));
359            s.push_str(&val);
360            s.push('\n');
361        }
362        s
363    }
364}
365
366impl UIfmt for AccessListItem {
367    fn pretty(&self) -> String {
368        let mut s = String::with_capacity(42 + self.storage_keys.len() * 66);
369        s.push_str(self.address.pretty().as_str());
370        s.push_str(" => ");
371        s.push_str(self.storage_keys.pretty().as_str());
372        s
373    }
374}
375
376impl UIfmt for TxLegacy {
377    fn pretty(&self) -> String {
378        format!(
379            "
380chainId              {}
381nonce                {}
382gasPrice             {}
383gasLimit             {}
384to                   {}
385value                {}
386input                {}",
387            self.chain_id.pretty(),
388            self.nonce.pretty(),
389            self.gas_price.pretty(),
390            self.gas_limit.pretty(),
391            self.to().pretty(),
392            self.value.pretty(),
393            self.input.pretty(),
394        )
395    }
396}
397
398impl UIfmt for TxEip2930 {
399    fn pretty(&self) -> String {
400        format!(
401            "
402chainId              {}
403nonce                {}
404gasPrice             {}
405gasLimit             {}
406to                   {}
407value                {}
408accessList           {}
409input                {}",
410            self.chain_id.pretty(),
411            self.nonce.pretty(),
412            self.gas_price.pretty(),
413            self.gas_limit.pretty(),
414            self.to().pretty(),
415            self.value.pretty(),
416            self.access_list.pretty(),
417            self.input.pretty(),
418        )
419    }
420}
421
422impl UIfmt for TxEip1559 {
423    fn pretty(&self) -> String {
424        format!(
425            "
426chainId              {}
427nonce                {}
428gasLimit             {}
429maxFeePerGas         {}
430maxPriorityFeePerGas {}
431to                   {}
432value                {}
433accessList           {}
434input                {}",
435            self.chain_id.pretty(),
436            self.nonce.pretty(),
437            self.gas_limit.pretty(),
438            self.max_fee_per_gas.pretty(),
439            self.max_priority_fee_per_gas.pretty(),
440            self.to().pretty(),
441            self.value.pretty(),
442            self.access_list.pretty(),
443            self.input.pretty(),
444        )
445    }
446}
447
448impl UIfmt for TxEip4844Variant {
449    fn pretty(&self) -> String {
450        use alloy_consensus::TxEip4844;
451        let tx: &TxEip4844 = match self {
452            Self::TxEip4844(tx) => tx,
453            Self::TxEip4844WithSidecar(tx) => tx.tx(),
454        };
455        format!(
456            "
457chainId              {}
458nonce                {}
459gasLimit             {}
460maxFeePerGas         {}
461maxPriorityFeePerGas {}
462to                   {}
463value                {}
464accessList           {}
465blobVersionedHashes  {}
466maxFeePerBlobGas     {}
467input                {}",
468            tx.chain_id.pretty(),
469            tx.nonce.pretty(),
470            tx.gas_limit.pretty(),
471            tx.max_fee_per_gas.pretty(),
472            tx.max_priority_fee_per_gas.pretty(),
473            tx.to.pretty(),
474            tx.value.pretty(),
475            tx.access_list.pretty(),
476            tx.blob_versioned_hashes.pretty(),
477            tx.max_fee_per_blob_gas.pretty(),
478            tx.input.pretty(),
479        )
480    }
481}
482
483impl UIfmt for TxEip7702 {
484    fn pretty(&self) -> String {
485        format!(
486            "
487chainId              {}
488nonce                {}
489gasLimit             {}
490maxFeePerGas         {}
491maxPriorityFeePerGas {}
492to                   {}
493value                {}
494accessList           {}
495authorizationList    {}
496input                {}",
497            self.chain_id.pretty(),
498            self.nonce.pretty(),
499            self.gas_limit.pretty(),
500            self.max_fee_per_gas.pretty(),
501            self.max_priority_fee_per_gas.pretty(),
502            self.to.pretty(),
503            self.value.pretty(),
504            self.access_list.pretty(),
505            self.authorization_list.pretty(),
506            self.input.pretty(),
507        )
508    }
509}
510
511#[cfg(feature = "optimism")]
512impl UIfmt for TxDeposit {
513    fn pretty(&self) -> String {
514        format!(
515            "
516sourceHash           {}
517from                 {}
518to                   {}
519mint                 {}
520value                {}
521gasLimit             {}
522isSystemTransaction  {}
523input                {}",
524            self.source_hash.pretty(),
525            self.from.pretty(),
526            self.to().pretty(),
527            self.mint.pretty(),
528            self.value.pretty(),
529            self.gas_limit.pretty(),
530            self.is_system_transaction,
531            self.input.pretty(),
532        )
533    }
534}
535
536#[cfg(feature = "base")]
537impl UIfmt for BaseTxDeposit {
538    fn pretty(&self) -> String {
539        format!(
540            "
541sourceHash           {}
542from                 {}
543to                   {}
544mint                 {}
545value                {}
546gasLimit             {}
547isSystemTransaction  {}
548input                {}",
549            self.source_hash.pretty(),
550            self.from.pretty(),
551            self.to().pretty(),
552            self.mint.pretty(),
553            self.value.pretty(),
554            self.gas_limit.pretty(),
555            self.is_system_transaction,
556            self.input.pretty(),
557        )
558    }
559}
560
561#[cfg(feature = "base")]
562impl UIfmt for Eip8130Signed {
563    fn pretty(&self) -> String {
564        let tx = self.tx();
565        // `calls` is grouped into phases, and `accountChanges` can be long, so both are summarized
566        // by count rather than dumped inline.
567        format!(
568            "
569chainId              {}
570sender               {}
571payer                {}
572nonceKey             {}
573nonceSequence        {}
574validAfter           {}
575validBefore          {}
576maxFeePerGas         {}
577maxPriorityFeePerGas {}
578gasLimit             {}
579accountChanges       {}
580callPhases           {}
581calls                {}
582metadata             {}
583senderAuth           {}
584payerAuth            {}",
585            tx.chain_id.pretty(),
586            tx.sender.pretty(),
587            tx.payer.pretty(),
588            tx.nonce_key.pretty(),
589            tx.nonce_sequence.pretty(),
590            tx.valid_after.pretty(),
591            tx.valid_before.pretty(),
592            tx.max_fee_per_gas.pretty(),
593            tx.max_priority_fee_per_gas.pretty(),
594            tx.gas_limit.pretty(),
595            tx.account_changes.len(),
596            tx.calls.len(),
597            tx.calls.iter().map(Vec::len).sum::<usize>(),
598            tx.metadata.pretty(),
599            self.sender_auth().pretty(),
600            self.payer_auth().pretty(),
601        )
602    }
603}
604
605#[cfg(feature = "optimism")]
606impl UIfmt for TxPostExec {
607    fn pretty(&self) -> String {
608        format!(
609            "
610blockNumber          {}
611gasRefundEntries     {:?}
612input                {}",
613            self.payload.block_number.pretty(),
614            self.payload.gas_refund_entries,
615            self.input.pretty(),
616        )
617    }
618}
619
620impl UIfmt for Call {
621    fn pretty(&self) -> String {
622        format!(
623            "to: {}, value: {}, input: {}",
624            self.to.into_to().pretty(),
625            self.value.pretty(),
626            self.input.pretty(),
627        )
628    }
629}
630
631impl UIfmt for TempoTransaction {
632    fn pretty(&self) -> String {
633        format!(
634            "
635chainId              {}
636feeToken             {}
637maxPriorityFeePerGas {}
638maxFeePerGas         {}
639gasLimit             {}
640calls                {}
641accessList           {}
642nonceKey             {}
643nonce                {}
644feePayerSignature    {}
645validBefore          {}
646validAfter           {}",
647            self.chain_id.pretty(),
648            self.fee_token.pretty(),
649            self.max_priority_fee_per_gas.pretty(),
650            self.max_fee_per_gas.pretty(),
651            self.gas_limit.pretty(),
652            self.calls.pretty(),
653            self.access_list.pretty(),
654            self.nonce_key.pretty(),
655            self.nonce.pretty(),
656            self.fee_payer_signature.pretty(),
657            self.valid_before.pretty(),
658            self.valid_after.pretty(),
659        )
660    }
661}
662
663impl UIfmt for TempoSignature {
664    fn pretty(&self) -> String {
665        serde_json::to_string(self).unwrap_or_default()
666    }
667}
668
669impl UIfmt for AASigned {
670    fn pretty(&self) -> String {
671        format!(
672            "
673hash                 {}
674type                 {}
675{}
676tempoSignature       {}",
677            self.hash().pretty(),
678            self.tx().ty(),
679            self.tx().pretty().trim_start(),
680            self.signature().pretty(),
681        )
682    }
683}
684
685impl<T: UIfmt + Typed2718> UIfmt for Signed<T>
686where
687    Self: TxHashRef,
688{
689    fn pretty(&self) -> String {
690        format!(
691            "
692hash                 {}
693type                 {}
694{}
695r                    {}
696s                    {}
697yParity              {}",
698            self.tx_hash().pretty(),
699            self.ty(),
700            self.tx().pretty().trim_start(),
701            FixedBytes::from(self.signature().r()).pretty(),
702            FixedBytes::from(self.signature().s()).pretty(),
703            (if self.signature().v() { 1u64 } else { 0 }).pretty(),
704        )
705    }
706}
707
708impl UIfmt for TxEnvelope {
709    fn pretty(&self) -> String {
710        match self {
711            Self::Legacy(tx) => tx.pretty(),
712            Self::Eip2930(tx) => tx.pretty(),
713            Self::Eip1559(tx) => tx.pretty(),
714            Self::Eip4844(tx) => tx.pretty(),
715            Self::Eip7702(tx) => tx.pretty(),
716        }
717    }
718}
719
720#[cfg(feature = "base")]
721impl UIfmt for BaseTxEnvelope {
722    fn pretty(&self) -> String {
723        match self {
724            Self::Legacy(tx) => tx.pretty(),
725            Self::Eip2930(tx) => tx.pretty(),
726            Self::Eip1559(tx) => tx.pretty(),
727            Self::Eip7702(tx) => tx.pretty(),
728            Self::Deposit(tx) => tx.inner().pretty(),
729            Self::Eip8130(tx) => tx.pretty(),
730        }
731    }
732}
733
734impl UIfmt for AnyTxEnvelope {
735    fn pretty(&self) -> String {
736        match self {
737            Self::Ethereum(envelop) => envelop.pretty(),
738            Self::Unknown(tx) => {
739                format!(
740                    "
741hash                 {}
742type               {:#x}
743{}
744                    ",
745                    tx.hash.pretty(),
746                    tx.ty(),
747                    tx.inner.fields.pretty().trim_start(),
748                )
749            }
750        }
751    }
752}
753
754#[cfg(feature = "optimism")]
755impl UIfmt for OpTxEnvelope {
756    fn pretty(&self) -> String {
757        match self {
758            Self::Legacy(tx) => tx.pretty(),
759            Self::Eip2930(tx) => tx.pretty(),
760            Self::Eip1559(tx) => tx.pretty(),
761            Self::Eip7702(tx) => tx.pretty(),
762            Self::Deposit(tx) => tx.pretty(),
763            Self::PostExec(tx) => tx.pretty(),
764        }
765    }
766}
767
768impl UIfmt for TempoTxEnvelope {
769    fn pretty(&self) -> String {
770        match self {
771            Self::Legacy(tx) => tx.pretty(),
772            Self::Eip2930(tx) => tx.pretty(),
773            Self::Eip1559(tx) => tx.pretty(),
774            Self::Eip7702(tx) => tx.pretty(),
775            Self::AA(tx) => tx.pretty(),
776        }
777    }
778}
779
780impl<T: UIfmt> UIfmt for Transaction<T> {
781    fn pretty(&self) -> String {
782        format!(
783            "
784blockHash            {}
785blockNumber          {}
786from                 {}
787transactionIndex     {}
788effectiveGasPrice    {}
789{}",
790            self.block_hash.pretty(),
791            self.block_number.pretty(),
792            self.inner.signer().pretty(),
793            self.transaction_index.pretty(),
794            self.effective_gas_price.pretty(),
795            self.inner.inner().pretty().trim_start(),
796        )
797    }
798}
799
800#[cfg(feature = "base")]
801impl UIfmt for BaseRpcTransaction {
802    fn pretty(&self) -> String {
803        format!(
804            "
805depositNonce         {}
806depositReceiptVersion {}
807{}",
808            self.deposit_nonce.pretty(),
809            self.deposit_receipt_version.pretty(),
810            self.inner.pretty().trim_start(),
811        )
812    }
813}
814
815#[cfg(feature = "optimism")]
816impl<T: UIfmt> UIfmt for op_alloy_rpc_types::Transaction<T> {
817    fn pretty(&self) -> String {
818        format!(
819            "
820depositNonce         {}
821depositReceiptVersion {}
822{}",
823            self.deposit_nonce.pretty(),
824            self.deposit_receipt_version.pretty(),
825            self.inner.pretty().trim_start(),
826        )
827    }
828}
829
830impl UIfmt for AnyRpcBlock {
831    fn pretty(&self) -> String {
832        self.0.pretty()
833    }
834}
835
836impl UIfmt for AnyRpcTransaction {
837    fn pretty(&self) -> String {
838        self.0.pretty()
839    }
840}
841
842impl<T: UIfmt> UIfmt for WithOtherFields<T> {
843    fn pretty(&self) -> String {
844        format!("{}{}", self.inner.pretty(), self.other.pretty())
845    }
846}
847
848/// Various numerical ethereum types used for pretty printing
849#[derive(Clone, Debug, Deserialize)]
850#[serde(untagged)]
851#[expect(missing_docs)]
852pub enum EthValue {
853    U64(U64),
854    Address(Address),
855    U256(U256),
856    U64Array(Vec<U64>),
857    U256Array(Vec<U256>),
858    Other(serde_json::Value),
859}
860
861impl From<serde_json::Value> for EthValue {
862    fn from(val: serde_json::Value) -> Self {
863        serde_json::from_value(val).expect("infallible")
864    }
865}
866
867impl UIfmt for EthValue {
868    fn pretty(&self) -> String {
869        match self {
870            Self::U64(num) => num.pretty(),
871            Self::U256(num) => num.pretty(),
872            Self::Address(addr) => addr.pretty(),
873            Self::U64Array(arr) => arr.pretty(),
874            Self::U256Array(arr) => arr.pretty(),
875            Self::Other(val) => val.to_string().trim_matches('"').to_string(),
876        }
877    }
878}
879
880impl UIfmt for SignedAuthorization {
881    fn pretty(&self) -> String {
882        let signed_authorization = serde_json::to_string(self).unwrap_or("<invalid>".to_string());
883
884        match self.recover_authority() {
885            Ok(authority) => format!(
886                "{{recoveredAuthority: {authority}, signedAuthority: {signed_authorization}}}",
887            ),
888            Err(e) => format!(
889                "{{recoveredAuthority: <error: {e}>, signedAuthority: {signed_authorization}}}",
890            ),
891        }
892    }
893}
894
895pub trait UIfmtHeaderExt {
896    fn size_pretty(&self) -> String;
897    fn total_difficulty_pretty(&self) -> String;
898}
899
900impl UIfmtHeaderExt for Header {
901    fn size_pretty(&self) -> String {
902        self.size.pretty()
903    }
904
905    fn total_difficulty_pretty(&self) -> String {
906        self.total_difficulty.unwrap_or_else(|| self.difficulty()).pretty()
907    }
908}
909
910impl UIfmtHeaderExt for AnyRpcHeader {
911    fn size_pretty(&self) -> String {
912        self.size.pretty()
913    }
914
915    fn total_difficulty_pretty(&self) -> String {
916        self.total_difficulty.unwrap_or_else(|| self.difficulty()).pretty()
917    }
918}
919
920impl UIfmtHeaderExt for TempoHeaderResponse {
921    fn size_pretty(&self) -> String {
922        self.inner.size.pretty()
923    }
924
925    fn total_difficulty_pretty(&self) -> String {
926        self.inner.total_difficulty.unwrap_or_else(|| self.inner.difficulty()).pretty()
927    }
928}
929
930pub trait UIfmtSignatureExt {
931    fn signature_pretty(&self) -> Option<(String, String, String)>;
932}
933
934impl UIfmtSignatureExt for TxEnvelope {
935    fn signature_pretty(&self) -> Option<(String, String, String)> {
936        Some(pretty_signature_fields(self.signature()))
937    }
938}
939
940impl UIfmtSignatureExt for AnyTxEnvelope {
941    fn signature_pretty(&self) -> Option<(String, String, String)> {
942        self.as_envelope().and_then(|envelope| envelope.signature_pretty())
943    }
944}
945
946#[cfg(feature = "base")]
947impl UIfmtSignatureExt for BaseTxEnvelope {
948    fn signature_pretty(&self) -> Option<(String, String, String)> {
949        self.signature().map(pretty_signature_fields)
950    }
951}
952
953#[cfg(feature = "optimism")]
954impl UIfmtSignatureExt for OpTxEnvelope {
955    fn signature_pretty(&self) -> Option<(String, String, String)> {
956        self.signature().map(pretty_signature_fields)
957    }
958}
959
960impl UIfmtSignatureExt for TempoTxEnvelope {
961    fn signature_pretty(&self) -> Option<(String, String, String)> {
962        let sig = match self {
963            Self::Legacy(tx) => Some(tx.signature()),
964            Self::Eip2930(tx) => Some(tx.signature()),
965            Self::Eip1559(tx) => Some(tx.signature()),
966            Self::Eip7702(tx) => Some(tx.signature()),
967            Self::AA(tempo_tx) => {
968                if let TempoSignature::Primitive(PrimitiveSignature::Secp256k1(sig)) =
969                    tempo_tx.signature()
970                {
971                    Some(sig)
972                } else {
973                    None
974                }
975            }
976        }?;
977        Some(pretty_signature_fields(sig))
978    }
979}
980
981fn pretty_signature_fields(signature: &Signature) -> (String, String, String) {
982    (
983        FixedBytes::from(signature.r()).pretty(),
984        FixedBytes::from(signature.s()).pretty(),
985        U8::from_le_slice(&signature.as_bytes()[64..]).pretty(),
986    )
987}
988
989pub trait UIfmtReceiptExt {
990    fn logs_pretty(&self) -> String;
991    fn logs_bloom_pretty(&self) -> String;
992    fn tx_type_pretty(&self) -> String;
993}
994
995fn receipt_logs_pretty<T: TxReceipt>(receipt: &TransactionReceipt<T>) -> String
996where
997    T::Log: Serialize,
998{
999    serde_json::to_string(receipt.inner.logs()).unwrap_or_default()
1000}
1001
1002fn receipt_logs_bloom_pretty<T: TxReceipt>(receipt: &TransactionReceipt<T>) -> String {
1003    receipt.inner.bloom().pretty()
1004}
1005
1006impl UIfmtReceiptExt for TransactionReceipt {
1007    fn logs_pretty(&self) -> String {
1008        receipt_logs_pretty(self)
1009    }
1010
1011    fn logs_bloom_pretty(&self) -> String {
1012        receipt_logs_bloom_pretty(self)
1013    }
1014
1015    fn tx_type_pretty(&self) -> String {
1016        self.transaction_type().to_string()
1017    }
1018}
1019
1020#[cfg(feature = "base")]
1021impl UIfmtReceiptExt for BaseTransactionReceipt {
1022    fn logs_pretty(&self) -> String {
1023        receipt_logs_pretty(&self.inner)
1024    }
1025
1026    fn logs_bloom_pretty(&self) -> String {
1027        receipt_logs_bloom_pretty(&self.inner)
1028    }
1029
1030    fn tx_type_pretty(&self) -> String {
1031        self.inner.inner.receipt.tx_type().to_string()
1032    }
1033}
1034
1035impl UIfmtReceiptExt for AnyTransactionReceipt {
1036    fn logs_pretty(&self) -> String {
1037        receipt_logs_pretty(&self.inner)
1038    }
1039
1040    fn logs_bloom_pretty(&self) -> String {
1041        receipt_logs_bloom_pretty(&self.inner)
1042    }
1043
1044    fn tx_type_pretty(&self) -> String {
1045        self.inner.inner.r#type.to_string()
1046    }
1047}
1048
1049impl UIfmt for TempoTransactionReceipt {
1050    fn pretty(&self) -> String {
1051        let receipt = &self.inner;
1052
1053        let mut pretty = format!(
1054            "
1055blockHash            {}
1056blockNumber          {}
1057contractAddress      {}
1058cumulativeGasUsed    {}
1059effectiveGasPrice    {}
1060from                 {}
1061gasUsed              {}
1062logs                 {}
1063logsBloom            {}
1064root                 {}
1065status               {}
1066transactionHash      {}
1067transactionIndex     {}
1068type                 {}
1069feePayer             {}
1070feeToken             {}",
1071            receipt.block_hash().pretty(),
1072            receipt.block_number().pretty(),
1073            receipt.contract_address().pretty(),
1074            receipt.cumulative_gas_used().pretty(),
1075            receipt.effective_gas_price().pretty(),
1076            receipt.from().pretty(),
1077            receipt.gas_used().pretty(),
1078            serde_json::to_string(receipt.inner.logs()).unwrap(),
1079            receipt.inner.logs_bloom.pretty(),
1080            self.state_root().pretty(),
1081            receipt.status().pretty(),
1082            receipt.transaction_hash().pretty(),
1083            receipt.transaction_index().pretty(),
1084            receipt.inner.receipt.tx_type as u8,
1085            self.fee_payer.pretty(),
1086            self.fee_token.pretty(),
1087        );
1088
1089        if let Some(to) = receipt.to() {
1090            pretty.push_str(&format!("\nto                   {}", to.pretty()));
1091        }
1092
1093        pretty
1094    }
1095}
1096
1097impl UIfmtReceiptExt for TempoTransactionReceipt {
1098    fn logs_pretty(&self) -> String {
1099        serde_json::to_string(self.inner.inner.logs()).unwrap_or_default()
1100    }
1101
1102    fn logs_bloom_pretty(&self) -> String {
1103        self.inner.inner.logs_bloom.pretty()
1104    }
1105
1106    fn tx_type_pretty(&self) -> String {
1107        (self.inner.inner.receipt.tx_type as u8).to_string()
1108    }
1109}
1110
1111/// Returns the `UiFmt::pretty()` formatted attribute of the transactions
1112pub fn get_pretty_tx_attr<N>(transaction: &N::TransactionResponse, attr: &str) -> Option<String>
1113where
1114    N: Network,
1115    N::TxEnvelope: UIfmtSignatureExt,
1116{
1117    let (r, s, v) = transaction.as_ref().signature_pretty().unwrap_or_default();
1118    match attr {
1119        "blockHash" | "block_hash" => {
1120            Some(alloy_network::TransactionResponse::block_hash(transaction).pretty())
1121        }
1122        "blockNumber" | "block_number" => {
1123            Some(alloy_network::TransactionResponse::block_number(transaction).pretty())
1124        }
1125        "from" => Some(alloy_network::TransactionResponse::from(transaction).pretty()),
1126        "gas" => Some(TxTrait::gas_limit(transaction).pretty()),
1127        "gasPrice" | "gas_price" => Some(TxTrait::max_fee_per_gas(transaction).pretty()),
1128        "hash" => Some(alloy_network::TransactionResponse::tx_hash(transaction).pretty()),
1129        "input" => Some(TxTrait::input(transaction).pretty()),
1130        "nonce" => Some(TxTrait::nonce(transaction).to_string()),
1131        "s" => Some(s),
1132        "r" => Some(r),
1133        "to" => Some(TxTrait::to(transaction).pretty()),
1134        "transactionIndex" | "transaction_index" => {
1135            Some(alloy_network::TransactionResponse::transaction_index(transaction).pretty())
1136        }
1137        "v" => Some(v),
1138        "value" => Some(TxTrait::value(transaction).pretty()),
1139        _ => None,
1140    }
1141}
1142
1143pub fn get_pretty_block_attr<N>(block: &N::BlockResponse, attr: &str) -> Option<String>
1144where
1145    N: Network,
1146    N::BlockResponse: BlockResponse<Header = N::HeaderResponse>,
1147    N::HeaderResponse: UIfmtHeaderExt,
1148{
1149    match attr {
1150        "baseFeePerGas" | "base_fee_per_gas" => Some(block.header().base_fee_per_gas().pretty()),
1151        "difficulty" => Some(block.header().difficulty().pretty()),
1152        "extraData" | "extra_data" => Some(block.header().extra_data().pretty()),
1153        "gasLimit" | "gas_limit" => Some(block.header().gas_limit().pretty()),
1154        "gasUsed" | "gas_used" => Some(block.header().gas_used().pretty()),
1155        "hash" => Some(block.header().hash().pretty()),
1156        "logsBloom" | "logs_bloom" => Some(block.header().logs_bloom().pretty()),
1157        "miner" | "author" => Some(block.header().beneficiary().pretty()),
1158        "mixHash" | "mix_hash" => Some(block.header().mix_hash().pretty()),
1159        "nonce" => Some(block.header().nonce().pretty()),
1160        "number" => Some(block.header().number().pretty()),
1161        "parentHash" | "parent_hash" => Some(block.header().parent_hash().pretty()),
1162        "transactionsRoot" | "transactions_root" => {
1163            Some(block.header().transactions_root().pretty())
1164        }
1165        "receiptsRoot" | "receipts_root" => Some(block.header().receipts_root().pretty()),
1166        "sha3Uncles" | "sha_3_uncles" => Some(block.header().ommers_hash().pretty()),
1167        "size" => Some(block.header().size_pretty()),
1168        "stateRoot" | "state_root" => Some(block.header().state_root().pretty()),
1169        "timestamp" => Some(block.header().timestamp().pretty()),
1170        "totalDifficulty" | "total_difficulty" => Some(block.header().total_difficulty_pretty()),
1171        "blobGasUsed" | "blob_gas_used" => Some(block.header().blob_gas_used().pretty()),
1172        "excessBlobGas" | "excess_blob_gas" => Some(block.header().excess_blob_gas().pretty()),
1173        "requestsHash" | "requests_hash" => Some(block.header().requests_hash().pretty()),
1174        other => {
1175            if let Some(value) = block.other_fields().and_then(|fields| fields.get(other)) {
1176                let val = EthValue::from(value.clone());
1177                return Some(val.pretty());
1178            }
1179            None
1180        }
1181    }
1182}
1183
1184pub fn get_pretty_receipt_attr<N>(receipt: &N::ReceiptResponse, attr: &str) -> Option<String>
1185where
1186    N: Network,
1187    N::ReceiptResponse: ReceiptResponse + UIfmtReceiptExt,
1188{
1189    match attr {
1190        "blockHash" | "block_hash" => Some(receipt.block_hash().pretty()),
1191        "blockNumber" | "block_number" => Some(receipt.block_number().pretty()),
1192        "contractAddress" | "contract_address" => Some(receipt.contract_address().pretty()),
1193        "cumulativeGasUsed" | "cumulative_gas_used" => Some(receipt.cumulative_gas_used().pretty()),
1194        "effectiveGasPrice" | "effective_gas_price" => Some(receipt.effective_gas_price().pretty()),
1195        "from" => Some(receipt.from().pretty()),
1196        "gasUsed" | "gas_used" => Some(receipt.gas_used().pretty()),
1197        "logs" => Some(receipt.logs_pretty()),
1198        "logsBloom" | "logs_bloom" => Some(receipt.logs_bloom_pretty()),
1199        "root" | "stateRoot" | "state_root" => Some(receipt.state_root().pretty()),
1200        "status" | "statusCode" | "status_code" => Some(receipt.status().pretty()),
1201        "transactionHash" | "transaction_hash" => Some(receipt.transaction_hash().pretty()),
1202        "transactionIndex" | "transaction_index" => Some(receipt.transaction_index().pretty()),
1203        "to" => Some(receipt.to().pretty()),
1204        "type" | "transaction_type" => Some(receipt.tx_type_pretty()),
1205        "blobGasPrice" | "blob_gas_price" => Some(receipt.blob_gas_price().pretty()),
1206        "blobGasUsed" | "blob_gas_used" => Some(receipt.blob_gas_used().pretty()),
1207        _ => None,
1208    }
1209}
1210
1211fn pretty_generic_header_response<H: HeaderResponse + UIfmtHeaderExt>(header: &H) -> String {
1212    format!(
1213        "
1214baseFeePerGas        {}
1215difficulty           {}
1216extraData            {}
1217gasLimit             {}
1218gasUsed              {}
1219hash                 {}
1220logsBloom            {}
1221miner                {}
1222mixHash              {}
1223nonce                {}
1224number               {}
1225parentHash           {}
1226parentBeaconRoot     {}
1227transactionsRoot     {}
1228receiptsRoot         {}
1229sha3Uncles           {}
1230size                 {}
1231stateRoot            {}
1232timestamp            {} ({})
1233withdrawalsRoot      {}
1234totalDifficulty      {}
1235blobGasUsed          {}
1236excessBlobGas        {}
1237requestsHash         {}",
1238        header.base_fee_per_gas().pretty(),
1239        header.difficulty().pretty(),
1240        header.extra_data().pretty(),
1241        header.gas_limit().pretty(),
1242        header.gas_used().pretty(),
1243        header.hash().pretty(),
1244        header.logs_bloom().pretty(),
1245        header.beneficiary().pretty(),
1246        header.mix_hash().pretty(),
1247        header.nonce().pretty(),
1248        header.number().pretty(),
1249        header.parent_hash().pretty(),
1250        header.parent_beacon_block_root().pretty(),
1251        header.transactions_root().pretty(),
1252        header.receipts_root().pretty(),
1253        header.ommers_hash().pretty(),
1254        header.size_pretty(),
1255        header.state_root().pretty(),
1256        header.timestamp().pretty(),
1257        fmt_timestamp(header.timestamp()),
1258        header.withdrawals_root().pretty(),
1259        header.total_difficulty_pretty(),
1260        header.blob_gas_used().pretty(),
1261        header.excess_blob_gas().pretty(),
1262        header.requests_hash().pretty(),
1263    )
1264}
1265
1266/// Formats the timestamp to string
1267///
1268/// Assumes timestamp is seconds, but handles millis if it is too large
1269fn fmt_timestamp(timestamp: u64) -> String {
1270    // Tue Jan 19 2038 03:14:07 GMT+0000
1271    if timestamp > 2147483647 {
1272        // assume this is in millis, incorrectly set to millis by a node
1273        chrono::DateTime::from_timestamp_millis(timestamp as i64)
1274            .expect("block timestamp in range")
1275            .to_rfc3339()
1276    } else {
1277        // assume this is still in seconds
1278        chrono::DateTime::from_timestamp(timestamp as i64, 0)
1279            .expect("block timestamp in range")
1280            .to_rfc2822()
1281    }
1282}
1283
1284#[cfg(test)]
1285mod tests {
1286    use super::*;
1287    use alloy_network::Ethereum;
1288    use alloy_primitives::B256;
1289    use alloy_rpc_types::Authorization;
1290    use similar_asserts::assert_eq;
1291    use std::str::FromStr;
1292
1293    #[cfg(feature = "base")]
1294    use base_common_consensus::{Call as BaseCall, TxEip8130};
1295
1296    #[test]
1297    fn format_date_time() {
1298        // Fri Aug 29 2025 08:05:38 GMT+0000
1299        let timestamp = 1756454738u64;
1300
1301        let datetime = fmt_timestamp(timestamp);
1302        assert_eq!(datetime, "Fri, 29 Aug 2025 08:05:38 +0000");
1303        let datetime = fmt_timestamp(timestamp * 1000);
1304        assert_eq!(datetime, "2025-08-29T08:05:38+00:00");
1305    }
1306
1307    #[test]
1308    fn can_format_bytes32() {
1309        let val = hex::decode("7465737400000000000000000000000000000000000000000000000000000000")
1310            .unwrap();
1311        let mut b32 = [0u8; 32];
1312        b32.copy_from_slice(&val);
1313
1314        assert_eq!(
1315            b32.pretty(),
1316            "0x7465737400000000000000000000000000000000000000000000000000000000"
1317        );
1318        let b: Bytes = val.into();
1319        assert_eq!(b.pretty(), b32.pretty());
1320    }
1321
1322    #[cfg(feature = "base")]
1323    #[test]
1324    fn can_pretty_print_eip8130_transaction() {
1325        let base_call = BaseCall { to: Address::ZERO, data: Bytes::new() };
1326        let tx = TxEip8130 {
1327            chain_id: 8453,
1328            sender: Some(Address::with_last_byte(0x11)),
1329            payer: Some(Address::with_last_byte(0x22)),
1330            nonce_key: U256::from(7),
1331            nonce_sequence: 3,
1332            valid_after: 100,
1333            valid_before: 200,
1334            max_fee_per_gas: 1_000,
1335            max_priority_fee_per_gas: 10,
1336            gas_limit: 21_000,
1337            metadata: Bytes::from_static(&[0xab]),
1338            calls: vec![vec![base_call.clone(), base_call.clone()], vec![base_call]],
1339            ..Default::default()
1340        };
1341        let signed =
1342            Eip8130Signed::new(tx, Bytes::from_static(&[0x01]), Bytes::from_static(&[0x02]));
1343
1344        assert_eq!(
1345            signed.pretty().trim(),
1346            r"chainId              8453
1347sender               0x0000000000000000000000000000000000000011
1348payer                0x0000000000000000000000000000000000000022
1349nonceKey             7
1350nonceSequence        3
1351validAfter           100
1352validBefore          200
1353maxFeePerGas         1000
1354maxPriorityFeePerGas 10
1355gasLimit             21000
1356accountChanges       0
1357callPhases           2
1358calls                3
1359metadata             0xab
1360senderAuth           0x01
1361payerAuth            0x02"
1362        );
1363    }
1364
1365    #[cfg(feature = "base")]
1366    #[test]
1367    fn can_pretty_print_base_deposit_receipt_fields() {
1368        let receipt: BaseTransactionReceipt = serde_json::from_value(serde_json::json!({
1369            "blockHash": "0x9e6a0fb7e22159d943d760608cc36a0fb596d1ab3c997146f5b7c55c8c718c67",
1370            "blockNumber": "0x6cfef89",
1371            "contractAddress": null,
1372            "cumulativeGasUsed": "0xfa0d",
1373            "depositNonce": "0x8a2d11",
1374            "depositReceiptVersion": "0x1",
1375            "effectiveGasPrice": "0x0",
1376            "from": "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001",
1377            "gasUsed": "0xfa0d",
1378            "logs": [{
1379                "address": "0x4200000000000000000000000000000000000015",
1380                "topics": [],
1381                "data": "0x",
1382                "removed": false,
1383                "blockTimestampMs": "0x18bcfe568c8",
1384                "blockNumber": "0x6cfef89",
1385                "blockHash": null,
1386                "transactionHash": null,
1387                "transactionIndex": null,
1388                "logIndex": null
1389            }],
1390            "logsBloom": format!("0x{}", "00".repeat(256)),
1391            "status": "0x1",
1392            "to": "0x4200000000000000000000000000000000000015",
1393            "transactionHash": "0xb7c74afdeb7c89fb9de2c312f49b38cb7a850ba36e064734c5223a477e83fdc9",
1394            "transactionIndex": "0x0",
1395            "type": "0x7e",
1396            "l1GasPrice": "0x3ef12787",
1397            "l1GasUsed": "0x1177",
1398            "l1Fee": "0x5bf1ab43d",
1399            "l1BaseFeeScalar": "0x1",
1400            "l1BlobBaseFee": "0x600ab8f05e64",
1401            "l1BlobBaseFeeScalar": "0x1",
1402            "operatorFeeScalar": "0x1",
1403            "operatorFeeConstant": "0x1",
1404            "daFootprintGasScalar": "0x1"
1405        }))
1406        .unwrap();
1407
1408        let pretty = receipt.pretty();
1409        let logs = receipt.logs_pretty();
1410        assert_eq!(
1411            serde_json::from_str::<serde_json::Value>(&logs).unwrap()[0]["blockTimestampMs"],
1412            "0x18bcfe568c8"
1413        );
1414        assert!(pretty.contains(&format!("logs                 {logs}")), "{pretty}");
1415        assert_eq!(receipt.logs_bloom_pretty(), receipt.inner.inner.bloom().pretty());
1416        assert!(pretty.contains("l1Fee                24681034813"), "{pretty}");
1417        assert!(pretty.contains("operatorFeeScalar    1"), "{pretty}");
1418        assert!(pretty.contains("depositNonce         9055505"), "{pretty}");
1419        assert!(pretty.contains("depositReceiptVersion 1"), "{pretty}");
1420    }
1421
1422    #[cfg(feature = "optimism")]
1423    #[test]
1424    fn can_pretty_print_optimism_tx() {
1425        let s = r#"
1426        {
1427        "blockHash": "0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae",
1428        "blockNumber": "0x1b4",
1429        "from": "0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6",
1430        "gas": "0x11cbbdc",
1431        "gasPrice": "0x0",
1432        "hash": "0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f",
1433        "input": "0xd294f093",
1434        "nonce": "0xa2",
1435        "to": "0x4a16A42407AA491564643E1dfc1fd50af29794eF",
1436        "transactionIndex": "0x0",
1437        "value": "0x0",
1438        "v": "0x38",
1439        "r": "0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee",
1440        "s": "0xe804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583",
1441        "depositNonce": "",
1442        "depositReceiptVersion": "0x1"
1443    }
1444        "#;
1445
1446        let tx: op_alloy_rpc_types::Transaction<OpTxEnvelope> = serde_json::from_str(s).unwrap();
1447        assert_eq!(
1448            tx.pretty().trim(),
1449            r"
1450depositNonce         
1451depositReceiptVersion 1
1452blockHash            0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae
1453blockNumber          436
1454from                 0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6
1455transactionIndex     0
1456effectiveGasPrice    0
1457hash                 0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f
1458type                 0
1459chainId              10
1460nonce                162
1461gasPrice             0
1462gasLimit             18660316
1463to                   0x4a16A42407AA491564643E1dfc1fd50af29794eF
1464value                0
1465input                0xd294f093
1466r                    0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee
1467s                    0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583
1468yParity              1
1469"
1470            .trim()
1471        );
1472    }
1473
1474    #[cfg(feature = "optimism")]
1475    #[test]
1476    fn can_pretty_print_optimism_tx_through_any() {
1477        let s = r#"
1478        {
1479        "blockHash": "0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae",
1480        "blockNumber": "0x1b4",
1481        "from": "0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6",
1482        "gas": "0x11cbbdc",
1483        "gasPrice": "0x0",
1484        "hash": "0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f",
1485        "input": "0xd294f093",
1486        "nonce": "0xa2",
1487        "to": "0x4a16A42407AA491564643E1dfc1fd50af29794eF",
1488        "transactionIndex": "0x0",
1489        "value": "0x0",
1490        "v": "0x38",
1491        "r": "0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee",
1492        "s": "0xe804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583",
1493        "queueOrigin": "sequencer",
1494        "txType": "",
1495        "l1TxOrigin": null,
1496        "l1BlockNumber": "0xc1a65c",
1497        "l1Timestamp": "0x60d34b60",
1498        "index": "0x1b3",
1499        "queueIndex": null,
1500        "rawTransaction": "0xf86681a28084011cbbdc944a16a42407aa491564643e1dfc1fd50af29794ef8084d294f09338a06fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2beea00e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583"
1501    }
1502        "#;
1503
1504        let tx: WithOtherFields<Transaction<AnyTxEnvelope>> = serde_json::from_str(s).unwrap();
1505        assert_eq!(tx.pretty().trim(),
1506                   r"
1507blockHash            0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae
1508blockNumber          436
1509from                 0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6
1510transactionIndex     0
1511effectiveGasPrice    0
1512hash                 0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f
1513type                 0
1514chainId              10
1515nonce                162
1516gasPrice             0
1517gasLimit             18660316
1518to                   0x4a16A42407AA491564643E1dfc1fd50af29794eF
1519value                0
1520input                0xd294f093
1521r                    0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee
1522s                    0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583
1523yParity              1
1524index                435
1525l1BlockNumber        12691036
1526l1Timestamp          1624460128
1527l1TxOrigin           null
1528queueIndex           null
1529queueOrigin          sequencer
1530rawTransaction       0xf86681a28084011cbbdc944a16a42407aa491564643e1dfc1fd50af29794ef8084d294f09338a06fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2beea00e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583
1531txType               0
1532".trim()
1533        );
1534    }
1535
1536    #[test]
1537    fn can_pretty_print_eip2930() {
1538        let s = r#"{
1539        "type": "0x1",
1540        "blockHash": "0x2b27fe2bbc8ce01ac7ae8bf74f793a197cf7edbe82727588811fa9a2c4776f81",
1541        "blockNumber": "0x12b1d",
1542        "from": "0x2b371c0262ceab27face32fbb5270ddc6aa01ba4",
1543        "gas": "0x6bdf",
1544        "gasPrice": "0x3b9aca00",
1545        "hash": "0xbddbb685774d8a3df036ed9fb920b48f876090a57e9e90ee60921e0510ef7090",
1546        "input": "0x9c0e3f7a0000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000002a",
1547        "nonce": "0x1c",
1548        "to": "0x8e730df7c70d33118d9e5f79ab81aed0be6f6635",
1549        "transactionIndex": "0x2",
1550        "value": "0x0",
1551        "v": "0x1",
1552        "r": "0x2a98c51c2782f664d3ce571fef0491b48f5ebbc5845fa513192e6e6b24ecdaa1",
1553        "s": "0x29b8e0c67aa9c11327e16556c591dc84a7aac2f6fc57c7f93901be8ee867aebc",
1554        "chainId": "0x66a",
1555        "accessList": [
1556            { "address": "0x2b371c0262ceab27face32fbb5270ddc6aa01ba4", "storageKeys": ["0x1122334455667788990011223344556677889900112233445566778899001122", "0x0000000000000000000000000000000000000000000000000000000000000000"] },
1557            { "address": "0x8e730df7c70d33118d9e5f79ab81aed0be6f6635", "storageKeys": [] }
1558        ]
1559      }
1560        "#;
1561        let tx: Transaction = serde_json::from_str(s).unwrap();
1562        assert_eq!(tx.pretty().trim(),
1563                   r"
1564blockHash            0x2b27fe2bbc8ce01ac7ae8bf74f793a197cf7edbe82727588811fa9a2c4776f81
1565blockNumber          76573
1566from                 0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4
1567transactionIndex     2
1568effectiveGasPrice    1000000000
1569hash                 0xbddbb685774d8a3df036ed9fb920b48f876090a57e9e90ee60921e0510ef7090
1570type                 1
1571chainId              1642
1572nonce                28
1573gasPrice             1000000000
1574gasLimit             27615
1575to                   0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635
1576value                0
1577accessList           [
1578	0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4 => [
1579		0x1122334455667788990011223344556677889900112233445566778899001122
1580		0x0000000000000000000000000000000000000000000000000000000000000000
1581	]
1582	0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635 => []
1583]
1584input                0x9c0e3f7a0000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000002a
1585r                    0x2a98c51c2782f664d3ce571fef0491b48f5ebbc5845fa513192e6e6b24ecdaa1
1586s                    0x29b8e0c67aa9c11327e16556c591dc84a7aac2f6fc57c7f93901be8ee867aebc
1587yParity              1
1588".trim()
1589        );
1590    }
1591
1592    #[test]
1593    fn can_pretty_print_eip1559() {
1594        let s = r#"{
1595        "type": "0x2",
1596        "blockHash": "0x61abbe5e22738de0462046f5a5d6c4cd6bc1f3a6398e4457d5e293590e721125",
1597        "blockNumber": "0x7647",
1598        "from": "0xbaadf00d42264eeb3fafe6799d0b56cf55df0f00",
1599        "gas": "0x186a0",
1600        "hash": "0xa7231d4da0576fade5d3b9481f4cd52459ec59b9bbdbf4f60d6cd726b2a3a244",
1601        "input": "0x48600055323160015500",
1602        "nonce": "0x12c",
1603        "to": null,
1604        "transactionIndex": "0x41",
1605        "value": "0x0",
1606        "v": "0x1",
1607        "yParity": "0x1",
1608        "r": "0x396864e5f9132327defdb1449504252e1fa6bce73feb8cd6f348a342b198af34",
1609        "s": "0x44dbba72e6d3304104848277143252ee43627c82f02d1ef8e404e1bf97c70158",
1610        "gasPrice": "0x4a817c800",
1611        "maxFeePerGas": "0x4a817c800",
1612        "maxPriorityFeePerGas": "0x4a817c800",
1613        "chainId": "0x66a",
1614        "accessList": [
1615          {
1616            "address": "0xc141a9a7463e6c4716d9fc0c056c054f46bb2993",
1617            "storageKeys": [
1618              "0x0000000000000000000000000000000000000000000000000000000000000000"
1619            ]
1620          }
1621        ]
1622      }
1623"#;
1624        let tx: Transaction = serde_json::from_str(s).unwrap();
1625        assert_eq!(
1626            tx.pretty().trim(),
1627            r"
1628blockHash            0x61abbe5e22738de0462046f5a5d6c4cd6bc1f3a6398e4457d5e293590e721125
1629blockNumber          30279
1630from                 0xBaaDF00d42264eEb3FAFe6799d0b56cf55DF0F00
1631transactionIndex     65
1632effectiveGasPrice    20000000000
1633hash                 0xa7231d4da0576fade5d3b9481f4cd52459ec59b9bbdbf4f60d6cd726b2a3a244
1634type                 2
1635chainId              1642
1636nonce                300
1637gasLimit             100000
1638maxFeePerGas         20000000000
1639maxPriorityFeePerGas 20000000000
1640to                   
1641value                0
1642accessList           [
1643	0xC141a9A7463e6C4716d9FC0C056C054F46Bb2993 => [
1644		0x0000000000000000000000000000000000000000000000000000000000000000
1645	]
1646]
1647input                0x48600055323160015500
1648r                    0x396864e5f9132327defdb1449504252e1fa6bce73feb8cd6f348a342b198af34
1649s                    0x44dbba72e6d3304104848277143252ee43627c82f02d1ef8e404e1bf97c70158
1650yParity              1
1651"
1652            .trim()
1653        );
1654    }
1655
1656    #[test]
1657    fn can_pretty_print_eip4884() {
1658        let s = r#"{
1659        "blockHash": "0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052",
1660        "blockNumber": "0x2a1cb",
1661        "from": "0xad01b55d7c3448b8899862eb335fbb17075d8de2",
1662        "gas": "0x5208",
1663        "gasPrice": "0x1d1a94a201c",
1664        "maxFeePerGas": "0x1d1a94a201c",
1665        "maxPriorityFeePerGas": "0x1d1a94a201c",
1666        "maxFeePerBlobGas": "0x3e8",
1667        "hash": "0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00",
1668        "input": "0x",
1669        "nonce": "0x1b483",
1670        "to": "0x000000000000000000000000000000000000f1c1",
1671        "transactionIndex": "0x0",
1672        "value": "0x0",
1673        "type": "0x3",
1674        "accessList": [],
1675        "chainId": "0x1a1f0ff42",
1676        "blobVersionedHashes": [
1677          "0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76"
1678        ],
1679        "v": "0x0",
1680        "r": "0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1",
1681        "s": "0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b",
1682        "yParity": "0x0"
1683      }
1684"#;
1685        let tx: Transaction = serde_json::from_str(s).unwrap();
1686        assert_eq!(
1687            tx.pretty().trim(),
1688            r"
1689blockHash            0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052
1690blockNumber          172491
1691from                 0xAD01b55d7c3448B8899862eb335FBb17075d8DE2
1692transactionIndex     0
1693effectiveGasPrice    2000000000028
1694hash                 0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00
1695type                 3
1696chainId              7011893058
1697nonce                111747
1698gasLimit             21000
1699maxFeePerGas         2000000000028
1700maxPriorityFeePerGas 2000000000028
1701to                   0x000000000000000000000000000000000000f1C1
1702value                0
1703accessList           []
1704blobVersionedHashes  [
1705	0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76
1706]
1707maxFeePerBlobGas     1000
1708input                0x
1709r                    0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1
1710s                    0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b
1711yParity              0
1712"
1713            .trim()
1714        );
1715    }
1716
1717    #[test]
1718    fn print_block_w_txs() {
1719        let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#;
1720        let block: Block = serde_json::from_str(block).unwrap();
1721        let output = "
1722blockHash            0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972
1723blockNumber          3
1724from                 0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a
1725transactionIndex     0
1726effectiveGasPrice    20000000000
1727hash                 0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067
1728type                 0
1729chainId              1
1730nonce                2
1731gasPrice             20000000000
1732gasLimit             90000
1733to                   0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e
1734value                0
1735input                0x
1736r                    0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88
1737s                    0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e
1738yParity              0"
1739            .to_string();
1740        let txs = match block.transactions() {
1741            BlockTransactions::Full(txs) => txs,
1742            _ => panic!("not full transactions"),
1743        };
1744        let generated = txs[0].pretty();
1745        assert_eq!(generated.as_str(), output.as_str());
1746    }
1747
1748    #[test]
1749    fn uifmt_option_u64() {
1750        assert_eq!(None::<U64>.pretty(), "");
1751        assert_eq!(U64::from(100).pretty(), "100");
1752        assert_eq!(Some(U64::from(100)).pretty(), "100");
1753    }
1754
1755    #[test]
1756    fn uifmt_option_h64() {
1757        assert_eq!(None::<B256>.pretty(), "");
1758        assert_eq!(
1759            B256::with_last_byte(100).pretty(),
1760            "0x0000000000000000000000000000000000000000000000000000000000000064",
1761        );
1762        assert_eq!(
1763            Some(B256::with_last_byte(100)).pretty(),
1764            "0x0000000000000000000000000000000000000000000000000000000000000064",
1765        );
1766    }
1767
1768    #[test]
1769    fn uifmt_option_bytes() {
1770        assert_eq!(None::<Bytes>.pretty(), "");
1771        assert_eq!(
1772            Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000064")
1773                .unwrap()
1774                .pretty(),
1775            "0x0000000000000000000000000000000000000000000000000000000000000064",
1776        );
1777        assert_eq!(
1778            Some(
1779                Bytes::from_str(
1780                    "0x0000000000000000000000000000000000000000000000000000000000000064"
1781                )
1782                .unwrap()
1783            )
1784            .pretty(),
1785            "0x0000000000000000000000000000000000000000000000000000000000000064",
1786        );
1787    }
1788
1789    #[test]
1790    fn test_pretty_tx_attr() {
1791        let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#;
1792        let block: <Ethereum as Network>::BlockResponse = serde_json::from_str(block).unwrap();
1793        let txs = match block.transactions() {
1794            BlockTransactions::Full(txes) => txes,
1795            _ => panic!("not full transactions"),
1796        };
1797
1798        assert_eq!(None, get_pretty_tx_attr::<Ethereum>(&txs[0], ""));
1799        assert_eq!(Some("3".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "blockNumber"));
1800        assert_eq!(
1801            Some("0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a".to_string()),
1802            get_pretty_tx_attr::<Ethereum>(&txs[0], "from")
1803        );
1804        assert_eq!(Some("90000".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "gas"));
1805        assert_eq!(
1806            Some("20000000000".to_string()),
1807            get_pretty_tx_attr::<Ethereum>(&txs[0], "gasPrice")
1808        );
1809        assert_eq!(
1810            Some("0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067".to_string()),
1811            get_pretty_tx_attr::<Ethereum>(&txs[0], "hash")
1812        );
1813        assert_eq!(Some("0x".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "input"));
1814        assert_eq!(Some("2".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "nonce"));
1815        assert_eq!(
1816            Some("0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88".to_string()),
1817            get_pretty_tx_attr::<Ethereum>(&txs[0], "r")
1818        );
1819        assert_eq!(
1820            Some("0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e".to_string()),
1821            get_pretty_tx_attr::<Ethereum>(&txs[0], "s")
1822        );
1823        assert_eq!(
1824            Some("0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e".into()),
1825            get_pretty_tx_attr::<Ethereum>(&txs[0], "to")
1826        );
1827        assert_eq!(
1828            Some("0".to_string()),
1829            get_pretty_tx_attr::<Ethereum>(&txs[0], "transactionIndex")
1830        );
1831        assert_eq!(Some("27".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "v"));
1832        assert_eq!(Some("0".to_string()), get_pretty_tx_attr::<Ethereum>(&txs[0], "value"));
1833    }
1834
1835    #[test]
1836    fn test_pretty_block_attr() {
1837        let json = serde_json::json!(
1838        {
1839            "baseFeePerGas": "0x7",
1840            "miner": "0x0000000000000000000000000000000000000001",
1841            "number": "0x1b4",
1842            "hash": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
1843            "parentHash": "0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5",
1844            "mixHash": "0x1010101010101010101010101010101010101010101010101010101010101010",
1845            "nonce": "0x0000000000000000",
1846            "sealFields": [
1847              "0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2",
1848              "0x0000000000000042"
1849            ],
1850            "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1851            "logsBloom":  "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
1852            "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
1853            "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
1854            "stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff",
1855            "difficulty": "0x1",
1856            "totalDifficulty": "0x27f07",
1857            "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
1858            "size": "0x27f07",
1859            "gasLimit": "0x9f759",
1860            "minGasPrice": "0x9f759",
1861            "gasUsed": "0x9f759",
1862            "timestamp": "0x54e34e8e",
1863            "transactions": [],
1864            "uncles": [],
1865          }
1866        );
1867
1868        let block: <Ethereum as Network>::BlockResponse = serde_json::from_value(json).unwrap();
1869
1870        assert_eq!(None, get_pretty_block_attr::<Ethereum>(&block, ""));
1871        assert_eq!(
1872            Some("7".to_string()),
1873            get_pretty_block_attr::<Ethereum>(&block, "baseFeePerGas")
1874        );
1875        assert_eq!(Some("1".to_string()), get_pretty_block_attr::<Ethereum>(&block, "difficulty"));
1876        assert_eq!(
1877            Some("0x0000000000000000000000000000000000000000000000000000000000000000".to_string()),
1878            get_pretty_block_attr::<Ethereum>(&block, "extraData")
1879        );
1880        assert_eq!(
1881            Some("653145".to_string()),
1882            get_pretty_block_attr::<Ethereum>(&block, "gasLimit")
1883        );
1884        assert_eq!(
1885            Some("653145".to_string()),
1886            get_pretty_block_attr::<Ethereum>(&block, "gasUsed")
1887        );
1888        assert_eq!(
1889            Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()),
1890            get_pretty_block_attr::<Ethereum>(&block, "hash")
1891        );
1892        assert_eq!(Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), get_pretty_block_attr::<Ethereum>(&block, "logsBloom"));
1893        assert_eq!(
1894            Some("0x0000000000000000000000000000000000000001".to_string()),
1895            get_pretty_block_attr::<Ethereum>(&block, "miner")
1896        );
1897        assert_eq!(
1898            Some("0x1010101010101010101010101010101010101010101010101010101010101010".to_string()),
1899            get_pretty_block_attr::<Ethereum>(&block, "mixHash")
1900        );
1901        assert_eq!(
1902            Some("0x0000000000000000".to_string()),
1903            get_pretty_block_attr::<Ethereum>(&block, "nonce")
1904        );
1905        assert_eq!(Some("436".to_string()), get_pretty_block_attr::<Ethereum>(&block, "number"));
1906        assert_eq!(
1907            Some("0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5".to_string()),
1908            get_pretty_block_attr::<Ethereum>(&block, "parentHash")
1909        );
1910        assert_eq!(
1911            Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()),
1912            get_pretty_block_attr::<Ethereum>(&block, "transactionsRoot")
1913        );
1914        assert_eq!(
1915            Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()),
1916            get_pretty_block_attr::<Ethereum>(&block, "receiptsRoot")
1917        );
1918        assert_eq!(
1919            Some("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".to_string()),
1920            get_pretty_block_attr::<Ethereum>(&block, "sha3Uncles")
1921        );
1922        assert_eq!(Some("163591".to_string()), get_pretty_block_attr::<Ethereum>(&block, "size"));
1923        assert_eq!(
1924            Some("0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff".to_string()),
1925            get_pretty_block_attr::<Ethereum>(&block, "stateRoot")
1926        );
1927        assert_eq!(
1928            Some("1424182926".to_string()),
1929            get_pretty_block_attr::<Ethereum>(&block, "timestamp")
1930        );
1931        assert_eq!(
1932            Some("163591".to_string()),
1933            get_pretty_block_attr::<Ethereum>(&block, "totalDifficulty")
1934        );
1935
1936        let pretty = pretty_generic_header_response(block.header());
1937        assert!(pretty.contains("difficulty           1"), "{pretty}");
1938        assert!(pretty.contains("totalDifficulty      163591"), "{pretty}");
1939    }
1940
1941    #[test]
1942    fn test_receipt_other_fields_alignment() {
1943        let receipt_json = serde_json::json!(
1944        {
1945          "status": "0x1",
1946          "cumulativeGasUsed": "0x74e483",
1947          "logs": [],
1948          "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
1949          "type": "0x2",
1950          "transactionHash": "0x91181b0dca3b29aa136eeb2f536be5ce7b0aebc949be1c44b5509093c516097d",
1951          "transactionIndex": "0x10",
1952          "blockHash": "0x54bafb12e8cea9bb355fbf03a4ac49e42a2a1a80fa6cf4364b342e2de6432b5d",
1953          "blockNumber": "0x7b1ab93",
1954          "gasUsed": "0xc222",
1955          "effectiveGasPrice": "0x18961",
1956          "from": "0x2d815240a61731c75fa01b2793e1d3ed09f289d0",
1957          "to": "0x4200000000000000000000000000000000000000",
1958          "contractAddress": null,
1959          "l1BaseFeeScalar": "0x146b",
1960          "l1BlobBaseFee": "0x6a83078",
1961          "l1BlobBaseFeeScalar": "0xf79c5",
1962          "l1Fee": "0x51a9af7fd3",
1963          "l1GasPrice": "0x972fe4acc",
1964          "l1GasUsed": "0x640"
1965        });
1966
1967        let receipt: AnyTransactionReceipt = serde_json::from_value(receipt_json).unwrap();
1968        let formatted = receipt.pretty();
1969
1970        let expected = r#"
1971blockHash            0x54bafb12e8cea9bb355fbf03a4ac49e42a2a1a80fa6cf4364b342e2de6432b5d
1972blockNumber          129084307
1973contractAddress      
1974cumulativeGasUsed    7660675
1975effectiveGasPrice    100705
1976from                 0x2D815240A61731c75Fa01b2793E1D3eD09F289d0
1977gasUsed              49698
1978logs                 []
1979logsBloom            0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1980root                 
1981status               1 (success)
1982transactionHash      0x91181b0dca3b29aa136eeb2f536be5ce7b0aebc949be1c44b5509093c516097d
1983transactionIndex     16
1984type                 2
1985blobGasPrice         
1986blobGasUsed          
1987to                   0x4200000000000000000000000000000000000000
1988l1BaseFeeScalar      5227
1989l1BlobBaseFee        111685752
1990l1BlobBaseFeeScalar  1014213
1991l1Fee                350739202003
1992l1GasPrice           40583973580
1993l1GasUsed            1600
1994"#;
1995
1996        assert_eq!(formatted.trim(), expected.trim());
1997    }
1998
1999    #[test]
2000    fn test_uifmt_for_signed_authorization() {
2001        let inner = Authorization {
2002            chain_id: U256::from(1),
2003            address: "0x000000000000000000000000000000000000dead".parse::<Address>().unwrap(),
2004            nonce: 42,
2005        };
2006        let signed_authorization =
2007            SignedAuthorization::new_unchecked(inner, 1, U256::from(20), U256::from(30));
2008
2009        assert_eq!(
2010            signed_authorization.pretty(),
2011            r#"{recoveredAuthority: 0xf3eaBD0de6Ca1aE7fC4D81FfD6C9a40e5D5D7e30, signedAuthority: {"chainId":"0x1","address":"0x000000000000000000000000000000000000dead","nonce":"0x2a","yParity":"0x1","r":"0x14","s":"0x1e"}}"#
2012        );
2013    }
2014
2015    #[test]
2016    fn can_pretty_print_tempo_tx() {
2017        let s = r#"{
2018            "type":"0x76",
2019            "chainId":"0xa5bd",
2020            "feeToken":"0x20c0000000000000000000000000000000000001",
2021            "maxPriorityFeePerGas":"0x0",
2022            "maxFeePerGas":"0x2cb417800",
2023            "gas":"0x2d178",
2024            "calls":[
2025                {
2026                    "data":null,
2027                    "input":"0x095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680",
2028                    "to":"0x20c0000000000000000000000000000000000000",
2029                    "value":"0x0"
2030                },
2031                {
2032                    "data":null,
2033                    "input":"0xf8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330",
2034                    "to":"0xdec0000000000000000000000000000000000000",
2035                    "value":"0x0"
2036                }
2037            ],
2038            "accessList":[],
2039            "nonceKey":"0x0",
2040            "nonce":"0x0",
2041            "feePayerSignature":null,
2042            "validBefore":null,
2043            "validAfter":null,
2044            "keyAuthorization":null,
2045            "aaAuthorizationList":[],
2046            "signature":{
2047                "pubKeyX":"0xaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd",
2048                "pubKeyY":"0x87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e",
2049                "r":"0xcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f",
2050                "s":"0x74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85e",
2051                "type":"webAuthn",
2052                "webauthnData":"0x7b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657d"
2053            },
2054            "hash":"0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd",
2055            "blockHash":"0xc82b23589ceef5341ed307d33554714db6f9eefd4187a9ca8abc910a325b4689",
2056            "blockNumber":"0x321fde",
2057            "transactionIndex":"0x0",
2058            "from":"0x566ff0f4a6114f8072ecdc8a7a8a13d8d0c6b45f",
2059            "gasPrice":"0x2540be400"
2060        }"#;
2061
2062        let tx: Transaction<TempoTxEnvelope> = serde_json::from_str(s).unwrap();
2063
2064        assert_eq!(
2065            tx.pretty().trim(),
2066            r#"
2067blockHash            0xc82b23589ceef5341ed307d33554714db6f9eefd4187a9ca8abc910a325b4689
2068blockNumber          3284958
2069from                 0x566Ff0f4a6114F8072ecDC8A7A8A13d8d0C6B45F
2070transactionIndex     0
2071effectiveGasPrice    10000000000
2072hash                 0x6d6d8c102064e6dee44abad2024a8b1d37959230baab80e70efbf9b0c739c4fd
2073type                 118
2074chainId              42429
2075feeToken             0x20C0000000000000000000000000000000000001
2076maxPriorityFeePerGas 0
2077maxFeePerGas         12000000000
2078gasLimit             184696
2079calls                [
2080	to: 0x20C0000000000000000000000000000000000000, value: 0, input: 0x095ea7b3000000000000000000000000dec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000989680
2081	to: 0xDEc0000000000000000000000000000000000000, value: 0, input: 0xf8856c0f00000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020c00000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000097d330
2082]
2083accessList           []
2084nonceKey             0
2085nonce                0
2086feePayerSignature    
2087validBefore          
2088validAfter           
2089tempoSignature       {"type":"webAuthn","r":"0xcfd45c3b19745a42f80b134dcb02a8ba099a0e4e7be1984da54734aa81d8f29f","s":"0x74bb9170ae6d25bd510c83fe35895ee5712efe13980a5edc8094c534e23af85e","pubKeyX":"0xaacc80b21e45fb11f349424dce3a2f23547f60c0ff2f8bcaede2a247545ce8dd","pubKeyY":"0x87abf0dbb7a5c9507efae2e43833356651b45ac576c2e61cec4e9c0f41fcbf6e","webauthnData":"0x7b98b7a8e6c68d7eac741a52e6fdae0560ce3c16ef5427ad46d7a54d0ed86dd41d000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2238453071464a7a50585167546e645473643649456659457776323173516e626966374c4741776e4b43626b222c226f726967696e223a2268747470733a2f2f74656d706f2d6465782e76657263656c2e617070222c2263726f73734f726967696e223a66616c73657d"}
2090"#
2091                .trim()
2092        );
2093    }
2094
2095    #[test]
2096    fn can_pretty_print_tempo_receipt() {
2097        let s = r#"{"type":"0x76","status":"0x1","cumulativeGasUsed":"0x176d7f4","logs":[{"address":"0x20c0000000000000000000000000000000000000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x00000000000000000000000000000000000000000000000000000000000003e8","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb8","removed":false},{"address":"0x20c0000000000000000000000000000000000003","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x000000000000000000000000feec000000000000000000000000000000000000"],"data":"0x0000000000000000000000000000000000000000000000000000000000000417","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb9","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000010000000000000000000000000000000000000000000100000000000000000000000000000000040008000004200000000000000008000000000000000000040000000000000400000000000002000000000000000000000000000000000000000000000010000000000000000000000000000000000020000000000000800000000000000000000000000020000000000000000000000000000000000400000000000000000000000000000002000000000000000400000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","gasUsed":"0xcc6a","effectiveGasPrice":"0x4a817c802","from":"0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd","to":"0x20c0000000000000000000000000000000000000","contractAddress":null,"feeToken":"0x20c0000000000000000000000000000000000003","feePayer":"0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd"}"#;
2098
2099        let tx: TempoTransactionReceipt = serde_json::from_str(s).unwrap();
2100
2101        assert_eq!(
2102            tx.pretty().trim(),
2103            r#"
2104blockHash            0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66
2105blockNumber          6922711
2106contractAddress      
2107cumulativeGasUsed    24565748
2108effectiveGasPrice    20000000002
2109from                 0xa70ab0448e66cD77995bfBBa5c5b64B41a85F3fd
2110gasUsed              52330
2111logs                 [{"address":"0x20c0000000000000000000000000000000000000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x00000000000000000000000000000000000000000000000000000000000003e8","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb8","removed":false},{"address":"0x20c0000000000000000000000000000000000003","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000a70ab0448e66cd77995bfbba5c5b64b41a85f3fd","0x000000000000000000000000feec000000000000000000000000000000000000"],"data":"0x0000000000000000000000000000000000000000000000000000000000000417","blockHash":"0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66","blockNumber":"0x69a1d7","blockTimestamp":"0x69a5d790","transactionHash":"0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f","transactionIndex":"0x63","logIndex":"0xb9","removed":false}]
2112logsBloom            0x00000000000000000000000000000000000000000000010000000000000000000000000000000000000000000100000000000000000000000000000000040008000004200000000000000008000000000000000000040000000000000400000000000002000000000000000000000000000000000000000000000010000000000000000000000000000000000020000000000000800000000000000000000000000020000000000000000000000000000000000400000000000000000000000000000002000000000000000400000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000
2113root                 
2114status               true
2115transactionHash      0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f
2116transactionIndex     99
2117type                 118
2118feePayer             0xa70ab0448e66cD77995bfBBa5c5b64B41a85F3fd
2119feeToken             0x20C0000000000000000000000000000000000003
2120to                   0x20C0000000000000000000000000000000000000
2121"#
2122                .trim()
2123        );
2124    }
2125
2126    #[test]
2127    fn test_ethereum_receipt_uifmt() {
2128        let s = r#"{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x1234567890123456789012345678901234567890123456789012345678901234","transactionIndex":"0x0","blockHash":"0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd","blockNumber":"0x1","gasUsed":"0x5208","effectiveGasPrice":"0x3b9aca00","from":"0x1234567890123456789012345678901234567890","to":"0x0987654321098765432109876543210987654321","contractAddress":null}"#;
2129        let receipt: TransactionReceipt = serde_json::from_str(s).unwrap();
2130
2131        let pretty_output = receipt.pretty();
2132
2133        assert!(pretty_output.contains("blockHash"));
2134        assert!(pretty_output.contains("blockNumber"));
2135        assert!(pretty_output.contains("status"));
2136        assert!(pretty_output.contains("gasUsed"));
2137        assert!(pretty_output.contains("transactionHash"));
2138        assert!(pretty_output.contains("type"));
2139        assert!(
2140            pretty_output
2141                .contains("0x1234567890123456789012345678901234567890123456789012345678901234")
2142        );
2143        assert!(pretty_output.contains("1 (success)"));
2144        assert!(pretty_output.contains("0x0987654321098765432109876543210987654321"));
2145    }
2146
2147    #[test]
2148    fn test_get_pretty_receipt_attr() {
2149        let receipt_json = serde_json::json!({
2150            "type": "0x2",
2151            "status": "0x1",
2152            "cumulativeGasUsed": "0x5208",
2153            "logs": [],
2154            "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
2155            "transactionHash": "0x1234567890123456789012345678901234567890123456789012345678901234",
2156            "transactionIndex": "0x0",
2157            "blockHash": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd",
2158            "blockNumber": "0x1",
2159            "gasUsed": "0x5208",
2160            "effectiveGasPrice": "0x3b9aca00",
2161            "from": "0x1234567890123456789012345678901234567890",
2162            "to": "0x0987654321098765432109876543210987654321",
2163            "contractAddress": null
2164        });
2165
2166        let receipt: <Ethereum as Network>::ReceiptResponse =
2167            serde_json::from_value(receipt_json).unwrap();
2168
2169        // Test basic receipt attributes
2170        assert_eq!(
2171            Some("0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd".to_string()),
2172            get_pretty_receipt_attr::<Ethereum>(&receipt, "blockHash")
2173        );
2174        assert_eq!(
2175            Some("1".to_string()),
2176            get_pretty_receipt_attr::<Ethereum>(&receipt, "blockNumber")
2177        );
2178        assert_eq!(
2179            Some("0x1234567890123456789012345678901234567890123456789012345678901234".to_string()),
2180            get_pretty_receipt_attr::<Ethereum>(&receipt, "transactionHash")
2181        );
2182        assert_eq!(
2183            Some("21000".to_string()),
2184            get_pretty_receipt_attr::<Ethereum>(&receipt, "gasUsed")
2185        );
2186        assert_eq!(
2187            Some("true".to_string()),
2188            get_pretty_receipt_attr::<Ethereum>(&receipt, "status")
2189        );
2190        assert_eq!(
2191            Some("EIP-1559".to_string()),
2192            get_pretty_receipt_attr::<Ethereum>(&receipt, "type")
2193        );
2194        assert_eq!(Some("[]".to_string()), get_pretty_receipt_attr::<Ethereum>(&receipt, "logs"));
2195        assert!(get_pretty_receipt_attr::<Ethereum>(&receipt, "logsBloom").is_some());
2196    }
2197}