1use bytes::Bytes;
2use ethereum_types::{Address, Bloom, BloomInput, H256};
3use ethrex_crypto::Crypto;
4use ethrex_rlp::{
5 decode::{RLPDecode, get_rlp_bytes_item_payload, is_encoded_as_bytes},
6 encode::RLPEncode,
7 error::RLPDecodeError,
8 structs::{Decoder, Encoder},
9};
10use serde::{Deserialize, Serialize};
11
12use crate::types::TxType;
13pub type Index = u64;
14
15#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
17pub struct Receipt {
18 pub tx_type: TxType,
19 pub succeeded: bool,
20 pub cumulative_gas_used: u64,
24 pub logs: Vec<Log>,
25}
26
27impl Receipt {
28 pub fn new(tx_type: TxType, succeeded: bool, cumulative_gas_used: u64, logs: Vec<Log>) -> Self {
29 Self {
30 tx_type,
31 succeeded,
32 cumulative_gas_used,
33 logs,
34 }
35 }
36
37 pub fn encode_inner(&self) -> Vec<u8> {
38 let mut encoded_data = vec![];
39 let tx_type: u8 = self.tx_type as u8;
40 Encoder::new(&mut encoded_data)
41 .encode_field(&tx_type)
42 .encode_field(&self.succeeded)
43 .encode_field(&self.cumulative_gas_used)
44 .encode_field(&self.logs)
45 .finish();
46 encoded_data
47 }
48
49 pub fn encode_inner_with_bloom(&self, crypto: &dyn Crypto) -> Vec<u8> {
50 self.encode_inner_with_precomputed_bloom(bloom_from_logs(&self.logs, crypto))
51 }
52
53 pub fn encode_inner_with_precomputed_bloom(&self, bloom: Bloom) -> Vec<u8> {
57 let mut encode_buf = Vec::with_capacity(512);
60 if self.tx_type != TxType::Legacy {
61 encode_buf.push(self.tx_type as u8);
62 }
63 Encoder::new(&mut encode_buf)
64 .encode_field(&self.succeeded)
65 .encode_field(&self.cumulative_gas_used)
66 .encode_field(&bloom)
67 .encode_field(&self.logs)
68 .finish();
69 encode_buf
70 }
71}
72
73pub fn bloom_from_logs(logs: &[Log], crypto: &dyn Crypto) -> Bloom {
74 let mut bloom = Bloom::zero();
75 for log in logs {
76 let address_hash = crypto.keccak256(log.address.as_bytes());
77 bloom.accrue(BloomInput::Hash(&address_hash));
78 for topic in log.topics.iter() {
79 let topic_hash = crypto.keccak256(topic.as_bytes());
80 bloom.accrue(BloomInput::Hash(&topic_hash));
81 }
82 }
83 bloom
84}
85
86impl RLPEncode for Receipt {
87 fn encode(&self, buf: &mut dyn bytes::BufMut) {
88 let encoded_inner = self.encode_inner();
89 buf.put_slice(&encoded_inner);
90 }
91}
92
93impl RLPDecode for Receipt {
94 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
95 let decoder = Decoder::new(rlp)?;
96 let (tx_type, decoder): (u8, _) = decoder.decode_field("tx-type")?;
97 let (succeeded, decoder) = decoder.decode_field("succeeded")?;
98 let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
99 let (logs, decoder) = decoder.decode_field("logs")?;
100
101 let Some(tx_type) = TxType::from_u8(tx_type) else {
102 return Err(RLPDecodeError::Custom(
103 "Invalid transaction type".to_string(),
104 ));
105 };
106
107 Ok((
108 Receipt {
109 tx_type,
110 succeeded,
111 cumulative_gas_used,
112 logs,
113 },
114 decoder.finish()?,
115 ))
116 }
117}
118
119#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
121pub struct ReceiptWithBloom {
122 pub tx_type: TxType,
123 pub succeeded: bool,
124 pub cumulative_gas_used: u64,
128 pub bloom: Bloom,
129 pub logs: Vec<Log>,
130}
131
132impl ReceiptWithBloom {
133 pub fn new(tx_type: TxType, succeeded: bool, cumulative_gas_used: u64, logs: Vec<Log>) -> Self {
134 Self {
135 tx_type,
136 succeeded,
137 cumulative_gas_used,
138 bloom: bloom_from_logs(&logs, ðrex_crypto::NativeCrypto),
139 logs,
140 }
141 }
142
143 pub fn encode_inner(&self) -> Vec<u8> {
161 let mut encode_buff = match self.tx_type {
162 TxType::Legacy => {
163 vec![]
164 }
165 _ => {
166 vec![self.tx_type as u8]
167 }
168 };
169 Encoder::new(&mut encode_buff)
170 .encode_field(&self.succeeded)
171 .encode_field(&self.cumulative_gas_used)
172 .encode_field(&self.bloom)
173 .encode_field(&self.logs)
174 .finish();
175 encode_buff
176 }
177
178 pub fn decode_inner(rlp: &[u8]) -> Result<Self, RLPDecodeError> {
182 let (tx_type, rlp) = match rlp.first() {
184 Some(tx_type) if *tx_type < 0x7f => {
185 let tx_type = match tx_type {
186 0x0 => TxType::Legacy,
187 0x1 => TxType::EIP2930,
188 0x2 => TxType::EIP1559,
189 0x3 => TxType::EIP4844,
190 0x4 => TxType::EIP7702,
191 0x7d => TxType::FeeToken,
192 0x7e => TxType::Privileged,
193 ty => {
194 return Err(RLPDecodeError::Custom(format!(
195 "Invalid transaction type: {ty}"
196 )));
197 }
198 };
199 (tx_type, &rlp[1..])
200 }
201 _ => (TxType::Legacy, rlp),
202 };
203 let decoder = Decoder::new(rlp)?;
204 let (succeeded, decoder) = decoder.decode_field("succeeded")?;
205 let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
206 let (bloom, decoder) = decoder.decode_field("bloom")?;
207 let (logs, decoder) = decoder.decode_field("logs")?;
208 decoder.finish()?;
209
210 Ok(Self {
211 tx_type,
212 succeeded,
213 cumulative_gas_used,
214 bloom,
215 logs,
216 })
217 }
218}
219
220impl RLPEncode for ReceiptWithBloom {
221 fn encode(&self, buf: &mut dyn bytes::BufMut) {
225 match self.tx_type {
226 TxType::Legacy => {
227 let legacy_encoded = self.encode_inner();
228 buf.put_slice(&legacy_encoded);
229 }
230 _ => {
231 let typed_recepipt_encoded = self.encode_inner();
232 let bytes = Bytes::from(typed_recepipt_encoded);
233 bytes.encode(buf);
234 }
235 };
236 }
237}
238
239impl RLPDecode for ReceiptWithBloom {
240 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
244 let (tx_type, rlp) = if is_encoded_as_bytes(rlp)? {
247 let payload = get_rlp_bytes_item_payload(rlp)?;
248 let tx_type = match payload.first().ok_or(RLPDecodeError::InvalidLength)? {
249 0x0 => TxType::Legacy,
250 0x1 => TxType::EIP2930,
251 0x2 => TxType::EIP1559,
252 0x3 => TxType::EIP4844,
253 0x4 => TxType::EIP7702,
254 0x7d => TxType::FeeToken,
255 0x7e => TxType::Privileged,
256 ty => {
257 return Err(RLPDecodeError::Custom(format!(
258 "Invalid transaction type: {ty}"
259 )));
260 }
261 };
262 (tx_type, &payload[1..])
263 } else {
264 (TxType::Legacy, rlp)
265 };
266
267 let decoder = Decoder::new(rlp)?;
268 let (succeeded, decoder) = decoder.decode_field("succeeded")?;
269 let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
270 let (bloom, decoder) = decoder.decode_field("bloom")?;
271 let (logs, decoder) = decoder.decode_field("logs")?;
272
273 Ok((
274 ReceiptWithBloom {
275 tx_type,
276 succeeded,
277 cumulative_gas_used,
278 bloom,
279 logs,
280 },
281 decoder.finish()?,
282 ))
283 }
284}
285
286impl From<&Receipt> for ReceiptWithBloom {
287 fn from(receipt: &Receipt) -> Self {
288 Self {
289 tx_type: receipt.tx_type,
290 succeeded: receipt.succeeded,
291 cumulative_gas_used: receipt.cumulative_gas_used,
292 bloom: bloom_from_logs(&receipt.logs, ðrex_crypto::NativeCrypto),
293 logs: receipt.logs.clone(),
294 }
295 }
296}
297
298impl From<&ReceiptWithBloom> for Receipt {
299 fn from(receipt: &ReceiptWithBloom) -> Self {
300 Self {
301 tx_type: receipt.tx_type,
302 succeeded: receipt.succeeded,
303 cumulative_gas_used: receipt.cumulative_gas_used,
304 logs: receipt.logs.clone(),
305 }
306 }
307}
308
309#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
311pub struct Log {
312 pub address: Address,
313 pub topics: Vec<H256>,
314 pub data: Bytes,
315}
316
317impl RLPEncode for Log {
318 fn encode(&self, buf: &mut dyn bytes::BufMut) {
319 Encoder::new(buf)
320 .encode_field(&self.address)
321 .encode_field(&self.topics)
322 .encode_field(&self.data)
323 .finish();
324 }
325}
326
327impl RLPDecode for Log {
328 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
329 let decoder = Decoder::new(rlp)?;
330 let (address, decoder) = decoder.decode_field("address")?;
331 let (topics, decoder) = decoder.decode_field("topics")?;
332 let (data, decoder) = decoder.decode_field("data")?;
333 let log = Log {
334 address,
335 topics,
336 data,
337 };
338 Ok((log, decoder.finish()?))
339 }
340}
341
342#[cfg(test)]
343mod test {
344 use super::*;
345
346 fn h256_from_hex(s: &str) -> H256 {
347 H256::from_slice(&hex::decode(s).unwrap())
348 }
349
350 #[test]
351 fn test_encode_decode_receipt_legacy() {
352 let receipt = Receipt {
353 tx_type: TxType::Legacy,
354 succeeded: true,
355 cumulative_gas_used: 1200,
356 logs: vec![Log {
357 address: Address::random(),
358 topics: vec![],
359 data: Bytes::from_static(b"foo"),
360 }],
361 };
362 let encoded_receipt = receipt.encode_to_vec();
363 assert_eq!(receipt, Receipt::decode(&encoded_receipt).unwrap())
364 }
365
366 #[test]
367 fn test_encode_decode_receipt_non_legacy() {
368 let receipt = Receipt {
369 tx_type: TxType::EIP4844,
370 succeeded: true,
371 cumulative_gas_used: 1500,
372 logs: vec![Log {
373 address: Address::random(),
374 topics: vec![],
375 data: Bytes::from_static(b"bar"),
376 }],
377 };
378 let encoded_receipt = receipt.encode_to_vec();
379 assert_eq!(receipt, Receipt::decode(&encoded_receipt).unwrap())
380 }
381
382 #[test]
383 fn test_encode_decode_inner_receipt_legacy() {
384 let receipt = ReceiptWithBloom {
385 tx_type: TxType::Legacy,
386 succeeded: true,
387 cumulative_gas_used: 1200,
388 bloom: Bloom::random(),
389 logs: vec![Log {
390 address: Address::random(),
391 topics: vec![],
392 data: Bytes::from_static(b"foo"),
393 }],
394 };
395 let encoded_receipt = receipt.encode_inner();
396 assert_eq!(
397 receipt,
398 ReceiptWithBloom::decode_inner(&encoded_receipt).unwrap()
399 )
400 }
401
402 #[test]
403 fn test_encode_decode_receipt_inner_non_legacy() {
404 let receipt = ReceiptWithBloom {
405 tx_type: TxType::EIP4844,
406 succeeded: true,
407 cumulative_gas_used: 1500,
408 bloom: Bloom::random(),
409 logs: vec![Log {
410 address: Address::random(),
411 topics: vec![],
412 data: Bytes::from_static(b"bar"),
413 }],
414 };
415 let encoded_receipt = receipt.encode_inner();
416 assert_eq!(
417 receipt,
418 ReceiptWithBloom::decode_inner(&encoded_receipt).unwrap()
419 )
420 }
421
422 #[test]
423 fn test_encode_receipt_with_bloom() {
424 let receipt = Receipt {
425 tx_type: TxType::EIP1559,
426 succeeded: true,
427 cumulative_gas_used: 1500,
428 logs: vec![Log {
429 address: Address::random(),
430 topics: vec![
431 h256_from_hex(
432 "e70c0d1060ffbafc84e0e18d028245de3deeb0f41ecbade6562fa657d85ae945",
433 ),
434 h256_from_hex(
435 "e7e9cd61c8c6cb313324d785aa130fe50a7b9885e4d1d7700a327c5e9ae4e183",
436 ),
437 h256_from_hex(
438 "666d827b9db958c08f7186f127e3d9ea6a97288bcc4b527951ce493f6e2b76c4",
439 ),
440 h256_from_hex(
441 "28b4366544dccafad7b61138e9ada51706e85bb217a20cfa1c86e2648f8f369a",
442 ),
443 h256_from_hex(
444 "85cf9717f65c70d71cc6175f653512c13ce7b6a9bc5d9c2b9c49b2d2d6cb9536",
445 ),
446 ],
447 data: Bytes::from_static(b"bar"),
448 }],
449 };
450 let encoded_receipt = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
451
452 let correct_bloom = {
453 let mut bloom = Bloom::zero();
454 for log in receipt.logs {
455 bloom.accrue(BloomInput::Raw(log.address.as_ref()));
456 for topic in log.topics.iter() {
457 bloom.accrue(BloomInput::Raw(topic.as_ref()));
458 }
459 }
460 bloom
461 };
462 let receipt_with_bloom = ReceiptWithBloom::decode_inner(&encoded_receipt).unwrap();
463 assert_eq!(receipt_with_bloom.bloom, correct_bloom);
464 }
465}