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