Skip to main content

ethrex_rpc/types/
receipt.rs

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, &ethrex_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}