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