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