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