1use alloy_consensus::{Receipt, ReceiptEnvelope, ReceiptWithBloom, TxReceipt, Typed2718};
4use alloy_primitives::{Address, Bloom, TxKind};
5use alloy_rpc_types_eth::TransactionReceipt;
6use alloy_serde::WithOtherFields;
7use arb_primitives::ArbPrimitives;
8use reth_primitives_traits::SealedBlock;
9use reth_rpc_convert::transaction::{ConvertReceiptInput, ReceiptConverter};
10use reth_rpc_eth_types::EthApiError;
11
12use crate::header::l1_block_number_from_mix_hash;
13
14#[derive(Debug, Clone)]
16pub struct ArbReceiptConverter;
17
18impl ReceiptConverter<ArbPrimitives> for ArbReceiptConverter {
19 type RpcReceipt = WithOtherFields<TransactionReceipt>;
20 type Error = EthApiError;
21
22 fn convert_receipts(
23 &self,
24 receipts: Vec<ConvertReceiptInput<'_, ArbPrimitives>>,
25 ) -> Result<Vec<Self::RpcReceipt>, EthApiError> {
26 let results = receipts
29 .into_iter()
30 .map(|input| convert_single_receipt(input, None, false))
31 .collect();
32 Ok(results)
33 }
34
35 fn convert_receipts_with_block(
36 &self,
37 receipts: Vec<ConvertReceiptInput<'_, ArbPrimitives>>,
38 block: &SealedBlock<alloy_consensus::Block<arb_primitives::ArbTransactionSigned>>,
39 ) -> Result<Vec<Self::RpcReceipt>, Self::Error> {
40 let mix_hash = block.header().mix_hash;
41 let l1_block_number = l1_block_number_from_mix_hash(&mix_hash);
42 let arbos_version = u64::from_be_bytes(mix_hash.0[16..24].try_into().unwrap_or_default());
45 let collect_tips = arbos_version
48 == arb_chainspec::arbos_version::ARBOS_VERSION_COLLECT_TIPS_OLD
49 || (mix_hash.0[25] & 1) == 1;
50
51 let results = receipts
52 .into_iter()
53 .map(|input| convert_single_receipt(input, Some(l1_block_number), collect_tips))
54 .collect();
55 Ok(results)
56 }
57}
58
59fn convert_single_receipt(
60 input: ConvertReceiptInput<'_, ArbPrimitives>,
61 l1_block_number: Option<u64>,
62 collect_tips: bool,
63) -> WithOtherFields<TransactionReceipt> {
64 use alloy_consensus::{Transaction, transaction::TxHashRef};
65
66 let ConvertReceiptInput {
67 receipt,
68 tx,
69 gas_used,
70 next_log_index,
71 meta,
72 } = input;
73
74 let from = tx.signer();
75 let tx_hash = *tx.tx_hash();
76 let tx_type = tx.ty();
77
78 let (contract_address, to) = match tx.kind() {
79 TxKind::Create => (Some(from.create(tx.nonce())), None),
80 TxKind::Call(addr) => (None, Some(Address(*addr))),
81 };
82
83 let cumulative_gas_used = receipt.cumulative_gas_used();
84 let status = receipt.status_or_post_state();
85 let gas_used_for_l1 = receipt.gas_used_for_l1;
86
87 let rpc_logs: Vec<alloy_rpc_types_eth::Log> = receipt
89 .logs()
90 .iter()
91 .enumerate()
92 .map(|(i, log)| alloy_rpc_types_eth::Log {
93 inner: log.clone(),
94 block_hash: Some(meta.block_hash),
95 block_number: Some(meta.block_number),
96 block_timestamp: None,
97 transaction_hash: Some(tx_hash),
98 transaction_index: Some(meta.index),
99 log_index: Some(next_log_index as u64 + i as u64),
100 removed: false,
101 })
102 .collect();
103
104 let bloom: Bloom = receipt.logs().iter().collect();
105
106 let receipt_with_bloom = ReceiptWithBloom::new(
107 Receipt {
108 status,
109 cumulative_gas_used,
110 logs: rpc_logs,
111 },
112 bloom,
113 );
114
115 let envelope = match tx_type {
117 0x01 => ReceiptEnvelope::Eip2930(receipt_with_bloom),
118 0x02 => ReceiptEnvelope::Eip1559(receipt_with_bloom),
119 0x03 => ReceiptEnvelope::Eip4844(receipt_with_bloom),
120 0x04 => ReceiptEnvelope::Eip7702(receipt_with_bloom),
121 _ => ReceiptEnvelope::Legacy(receipt_with_bloom),
122 };
123
124 let base_fee = meta.base_fee.unwrap_or(0) as u128;
127 let effective_gas_price = if collect_tips {
128 match tx_type {
129 0x00 | 0x01 => tx.gas_price().unwrap_or(base_fee),
131 0x64 | 0x6A => 0,
133 0x65 | 0x66 | 0x68 | 0x69 => base_fee,
136 _ => {
138 let tip = tx.max_priority_fee_per_gas().unwrap_or(0);
139 let cap = tx.max_fee_per_gas();
140 base_fee.saturating_add(tip).min(cap)
141 }
142 }
143 } else {
144 base_fee
145 };
146
147 let base_receipt = TransactionReceipt {
148 inner: envelope,
149 transaction_hash: tx_hash,
150 transaction_index: Some(meta.index),
151 block_hash: Some(meta.block_hash),
152 block_number: Some(meta.block_number),
153 gas_used,
154 effective_gas_price,
155 blob_gas_used: None,
156 blob_gas_price: None,
157 from,
158 to,
159 contract_address,
160 };
161
162 let mut other = std::collections::BTreeMap::new();
164
165 if tx_type >= 0x64 {
168 other.insert(
169 "type".to_string(),
170 serde_json::to_value(format!("{tx_type:#x}")).unwrap_or_default(),
171 );
172 }
173
174 other.insert(
176 "gasUsedForL1".to_string(),
177 serde_json::to_value(format!("{:#x}", gas_used_for_l1)).unwrap_or_default(),
178 );
179
180 if let Some(l1_bn) = l1_block_number {
182 other.insert(
183 "l1BlockNumber".to_string(),
184 serde_json::to_value(format!("{l1_bn:#x}")).unwrap_or_default(),
185 );
186 }
187
188 if !receipt.multi_gas_used.is_zero() {
190 other.insert(
191 "multiGasUsed".to_string(),
192 serde_json::to_value(receipt.multi_gas_used).unwrap_or_default(),
193 );
194 }
195
196 WithOtherFields {
197 inner: base_receipt,
198 other: alloy_serde::OtherFields::new(other),
199 }
200}