1use ethrex_common::{
2 Address, Bloom, Bytes, H256,
3 constants::GAS_PER_BLOB,
4 evm::calculate_create_address,
5 serde_utils,
6 types::{
7 BlockHash, BlockHeader, BlockNumber, Log, Receipt, Transaction, TxKind, TxType,
8 bloom_from_logs,
9 },
10};
11use ethrex_crypto::NativeCrypto;
12
13use serde::{Deserialize, Serialize};
14
15use crate::utils::RpcErr;
16
17#[derive(Debug, Serialize, Deserialize)]
18pub struct RpcReceipt {
19 #[serde(flatten)]
20 pub receipt: RpcReceiptInfo,
21 pub logs: Vec<RpcLog>,
22 #[serde(flatten)]
23 pub tx_info: RpcReceiptTxInfo,
24 #[serde(flatten)]
25 pub block_info: RpcReceiptBlockInfo,
26}
27
28impl RpcReceipt {
29 pub fn new(
30 receipt: Receipt,
31 tx_info: RpcReceiptTxInfo,
32 block_info: RpcReceiptBlockInfo,
33 init_log_index: u64,
34 ) -> Self {
35 let mut logs = vec![];
36 let mut log_index = init_log_index;
37 for log in receipt.logs.clone() {
38 logs.push(RpcLog::new(log, log_index, &tx_info, &block_info));
39 log_index += 1;
40 }
41 Self {
42 receipt: receipt.into(),
43 logs,
44 tx_info,
45 block_info,
46 }
47 }
48}
49
50#[derive(Debug, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct RpcReceiptInfo {
53 #[serde(rename = "type")]
54 pub tx_type: TxType,
55 #[serde(with = "serde_utils::bool")]
56 pub status: bool,
57 #[serde(with = "serde_utils::u64::hex_str")]
58 pub cumulative_gas_used: u64,
59 pub logs_bloom: Bloom,
60}
61
62impl From<Receipt> for RpcReceiptInfo {
63 fn from(receipt: Receipt) -> Self {
64 Self {
65 tx_type: receipt.tx_type,
66 status: receipt.succeeded,
67 cumulative_gas_used: receipt.cumulative_gas_used,
68 logs_bloom: bloom_from_logs(&receipt.logs, ðrex_crypto::NativeCrypto),
69 }
70 }
71}
72
73#[derive(Debug, Serialize, Deserialize, Clone)]
74#[serde(rename_all = "camelCase")]
75pub struct RpcLog {
76 #[serde(flatten)]
77 pub log: RpcLogInfo,
78 #[serde(with = "serde_utils::u64::hex_str")]
79 pub log_index: u64,
80 pub removed: bool,
81 pub transaction_hash: H256,
82 #[serde(with = "serde_utils::u64::hex_str")]
83 pub transaction_index: u64,
84 pub block_hash: BlockHash,
85 #[serde(with = "serde_utils::u64::hex_str")]
86 pub block_number: BlockNumber,
87}
88
89impl RpcLog {
90 pub fn new(
91 log: Log,
92 log_index: u64,
93 tx_info: &RpcReceiptTxInfo,
94 block_info: &RpcReceiptBlockInfo,
95 ) -> RpcLog {
96 Self {
97 log: log.into(),
98 log_index,
99 removed: false,
100 transaction_hash: tx_info.transaction_hash,
101 transaction_index: tx_info.transaction_index,
102 block_hash: block_info.block_hash,
103 block_number: block_info.block_number,
104 }
105 }
106}
107
108#[derive(Debug, Serialize, Deserialize, Clone)]
109#[serde(rename_all = "camelCase")]
110pub struct RpcLogInfo {
111 pub address: Address,
112 pub topics: Vec<H256>,
113 #[serde(with = "serde_utils::bytes")]
114 pub data: Bytes,
115}
116
117impl From<Log> for RpcLogInfo {
118 fn from(log: Log) -> Self {
119 Self {
120 address: log.address,
121 topics: log.topics,
122 data: log.data,
123 }
124 }
125}
126
127#[derive(Debug, Serialize, Clone, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct RpcReceiptBlockInfo {
130 pub block_hash: BlockHash,
131 #[serde(with = "serde_utils::u64::hex_str")]
132 pub block_number: BlockNumber,
133}
134
135impl RpcReceiptBlockInfo {
136 pub fn from_block_header(block_header: BlockHeader) -> Self {
137 RpcReceiptBlockInfo {
138 block_hash: block_header.hash(),
139 block_number: block_header.number,
140 }
141 }
142}
143#[derive(Debug, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct RpcReceiptTxInfo {
146 pub transaction_hash: H256,
147 #[serde(with = "ethrex_common::serde_utils::u64::hex_str")]
148 pub transaction_index: u64,
149 pub from: Address,
150 pub to: Option<Address>,
151 pub contract_address: Option<Address>,
152 #[serde(with = "ethrex_common::serde_utils::u64::hex_str")]
153 pub gas_used: u64,
154 #[serde(with = "ethrex_common::serde_utils::u64::hex_str")]
155 pub effective_gas_price: u64,
156 #[serde(
157 skip_serializing_if = "Option::is_none",
158 with = "serde_utils::u64::hex_str_opt",
159 default = "Option::default"
160 )]
161 pub blob_gas_price: Option<u64>,
162 #[serde(
163 skip_serializing_if = "Option::is_none",
164 with = "serde_utils::u64::hex_str_opt",
165 default = "Option::default"
166 )]
167 pub blob_gas_used: Option<u64>,
168}
169
170impl RpcReceiptTxInfo {
171 pub fn from_transaction(
172 transaction: Transaction,
173 index: u64,
174 gas_used: u64,
175 block_blob_gas_price: u64,
176 base_fee_per_gas: Option<u64>,
177 ) -> Result<Self, RpcErr> {
178 let nonce = transaction.nonce();
179 let from = transaction.sender(&NativeCrypto)?;
180 let transaction_hash = transaction.hash();
181 let effective_gas_price =
182 u64::try_from(transaction.effective_gas_price(base_fee_per_gas).ok_or(
183 RpcErr::Internal("Could not get effective gas price from tx".into()),
184 )?)
185 .map_err(|_| RpcErr::Internal("effective gas price overflows u64".into()))?;
186 let transaction_index = index;
187 let (blob_gas_price, blob_gas_used) = match &transaction {
188 Transaction::EIP4844Transaction(tx) => (
189 Some(block_blob_gas_price),
190 Some(tx.blob_versioned_hashes.len() as u64 * GAS_PER_BLOB as u64),
191 ),
192 _ => (None, None),
193 };
194 let (contract_address, to) = match transaction.to() {
195 TxKind::Create => (Some(calculate_create_address(from, nonce)), None),
196 TxKind::Call(addr) => (None, Some(addr)),
197 };
198 Ok(Self {
199 transaction_hash,
200 transaction_index,
201 from,
202 to,
203 contract_address,
204 gas_used,
205 effective_gas_price,
206 blob_gas_price,
207 blob_gas_used,
208 })
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use ethrex_common::{
216 Bytes,
217 types::{Log, TxType},
218 };
219 use hex_literal::hex;
220
221 #[test]
222 fn serialize_receipt() {
223 let receipt = RpcReceipt::new(
224 Receipt {
225 tx_type: TxType::EIP4844,
226 succeeded: true,
227 cumulative_gas_used: 147,
228 logs: vec![Log {
229 address: Address::zero(),
230 topics: vec![],
231 data: Bytes::from_static(b"strawberry"),
232 }],
233 },
234 RpcReceiptTxInfo {
235 transaction_hash: H256::zero(),
236 transaction_index: 1,
237 from: Address::zero(),
238 to: Some(Address::from(hex!(
239 "7435ed30a8b4aeb0877cef0c6e8cffe834eb865f"
240 ))),
241 contract_address: None,
242 gas_used: 147,
243 effective_gas_price: 157,
244 blob_gas_price: None,
245 blob_gas_used: None,
246 },
247 RpcReceiptBlockInfo {
248 block_hash: BlockHash::zero(),
249 block_number: 3,
250 },
251 0,
252 );
253 let expected = r#"{"type":"0x3","status":"0x1","cumulativeGasUsed":"0x93","logsBloom":"0x00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","logs":[{"address":"0x0000000000000000000000000000000000000000","topics":[],"data":"0x73747261776265727279","logIndex":"0x0","removed":false,"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x1","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x3"}],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x1","from":"0x0000000000000000000000000000000000000000","to":"0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f","contractAddress":null,"gasUsed":"0x93","effectiveGasPrice":"0x9d","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x3"}"#;
254 assert_eq!(serde_json::to_string(&receipt).unwrap(), expected);
255 }
256}