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