foundry_common_fmt/
ui.rs

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