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