1use alloc::{vec, vec::Vec};
2use reth_primitives_traits::InMemorySize;
3
4use alloy_consensus::{
5 Eip2718EncodableReceipt, Eip658Value, ReceiptWithBloom, RlpDecodableReceipt,
6 RlpEncodableReceipt, TxReceipt, Typed2718,
7};
8use alloy_primitives::{Bloom, Log, B256};
9use alloy_rlp::{Decodable, Encodable, Header, RlpDecodable, RlpEncodable};
10use bytes::BufMut;
11use derive_more::{DerefMut, From, IntoIterator};
12use serde::{Deserialize, Serialize};
13
14use crate::TxType;
15
16pub use reth_primitives_traits::receipt::gas_spent_by_transactions;
18
19#[derive(
21 Clone, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable, Serialize, Deserialize,
22)]
23#[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::CompactZstd))]
24#[cfg_attr(any(test, feature = "reth-codec"), reth_zstd(
25 compressor = reth_zstd_compressors::RECEIPT_COMPRESSOR,
26 decompressor = reth_zstd_compressors::RECEIPT_DECOMPRESSOR
27))]
28#[rlp(trailing)]
29pub struct Receipt {
30 pub tx_type: TxType,
32 pub success: bool,
36 pub cumulative_gas_used: u64,
38 pub logs: Vec<Log>,
40 #[cfg(feature = "optimism")]
42 pub deposit_nonce: Option<u64>,
43 #[cfg(feature = "optimism")]
50 pub deposit_receipt_version: Option<u64>,
51}
52
53impl Receipt {
54 pub fn bloom_slow(&self) -> Bloom {
57 alloy_primitives::logs_bloom(self.logs.iter())
58 }
59
60 pub fn with_bloom(self) -> ReceiptWithBloom<Self> {
63 self.into()
64 }
65
66 pub fn with_bloom_ref(&self) -> ReceiptWithBloom<&Self> {
69 self.into()
70 }
71
72 pub fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize {
74 let len = self.success.length() +
75 self.cumulative_gas_used.length() +
76 bloom.length() +
77 self.logs.length();
78
79 #[cfg(feature = "optimism")]
80 if self.tx_type == TxType::Deposit {
81 let mut len = len;
82
83 if let Some(deposit_nonce) = self.deposit_nonce {
84 len += deposit_nonce.length();
85 }
86 if let Some(deposit_receipt_version) = self.deposit_receipt_version {
87 len += deposit_receipt_version.length();
88 }
89
90 return len
91 }
92
93 len
94 }
95
96 pub fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn BufMut) {
98 self.success.encode(out);
99 self.cumulative_gas_used.encode(out);
100 bloom.encode(out);
101 self.logs.encode(out);
102
103 #[cfg(feature = "optimism")]
104 if self.tx_type == TxType::Deposit {
105 if let Some(nonce) = self.deposit_nonce {
106 nonce.encode(out);
107 }
108 if let Some(version) = self.deposit_receipt_version {
109 version.encode(out);
110 }
111 }
112 }
113
114 pub fn rlp_header_inner(&self, bloom: &Bloom) -> Header {
116 Header { list: true, payload_length: self.rlp_encoded_fields_length(bloom) }
117 }
118
119 fn decode_receipt_with_bloom(
120 buf: &mut &[u8],
121 tx_type: TxType,
122 ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
123 let b = &mut &**buf;
124 let rlp_head = alloy_rlp::Header::decode(b)?;
125 if !rlp_head.list {
126 return Err(alloy_rlp::Error::UnexpectedString)
127 }
128 let started_len = b.len();
129
130 let success = Decodable::decode(b)?;
131 let cumulative_gas_used = Decodable::decode(b)?;
132 let bloom = Decodable::decode(b)?;
133 let logs = Decodable::decode(b)?;
134
135 let receipt = match tx_type {
136 #[cfg(feature = "optimism")]
137 TxType::Deposit => {
138 let remaining = |b: &[u8]| rlp_head.payload_length - (started_len - b.len()) > 0;
139 let deposit_nonce = remaining(b).then(|| Decodable::decode(b)).transpose()?;
140 let deposit_receipt_version =
141 remaining(b).then(|| Decodable::decode(b)).transpose()?;
142
143 Self {
144 tx_type,
145 success,
146 cumulative_gas_used,
147 logs,
148 deposit_nonce,
149 deposit_receipt_version,
150 }
151 }
152 _ => Self {
153 tx_type,
154 success,
155 cumulative_gas_used,
156 logs,
157 #[cfg(feature = "optimism")]
158 deposit_nonce: None,
159 #[cfg(feature = "optimism")]
160 deposit_receipt_version: None,
161 },
162 };
163
164 let this = ReceiptWithBloom { receipt, logs_bloom: bloom };
165 let consumed = started_len - b.len();
166 if consumed != rlp_head.payload_length {
167 return Err(alloy_rlp::Error::ListLengthMismatch {
168 expected: rlp_head.payload_length,
169 got: consumed,
170 })
171 }
172 *buf = *b;
173 Ok(this)
174 }
175}
176
177impl Eip2718EncodableReceipt for Receipt {
178 fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
179 self.rlp_header_inner(bloom).length_with_payload() +
180 !matches!(self.tx_type, TxType::Legacy) as usize }
182
183 fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
184 if !matches!(self.tx_type, TxType::Legacy) {
185 out.put_u8(self.tx_type as u8);
186 }
187 self.rlp_header_inner(bloom).encode(out);
188 self.rlp_encode_fields(bloom, out);
189 }
190}
191
192impl RlpEncodableReceipt for Receipt {
193 fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
194 let mut len = self.eip2718_encoded_length_with_bloom(bloom);
195 if !matches!(self.tx_type, TxType::Legacy) {
196 len += Header {
197 list: false,
198 payload_length: self.eip2718_encoded_length_with_bloom(bloom),
199 }
200 .length();
201 }
202
203 len
204 }
205
206 fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
207 if !matches!(self.tx_type, TxType::Legacy) {
208 Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom) }
209 .encode(out);
210 }
211 self.eip2718_encode_with_bloom(bloom, out);
212 }
213}
214
215impl RlpDecodableReceipt for Receipt {
216 fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
217 let header_buf = &mut &**buf;
218 let header = Header::decode(header_buf)?;
219
220 if header.list {
221 return Self::decode_receipt_with_bloom(buf, TxType::Legacy);
222 }
223
224 *buf = *header_buf;
225
226 let remaining = buf.len();
227 let tx_type = TxType::decode(buf)?;
228 let this = Self::decode_receipt_with_bloom(buf, tx_type)?;
229
230 if buf.len() + header.payload_length != remaining {
231 return Err(alloy_rlp::Error::UnexpectedLength);
232 }
233
234 Ok(this)
235 }
236}
237
238impl TxReceipt for Receipt {
239 type Log = Log;
240
241 fn status_or_post_state(&self) -> Eip658Value {
242 self.success.into()
243 }
244
245 fn status(&self) -> bool {
246 self.success
247 }
248
249 fn bloom(&self) -> Bloom {
250 alloy_primitives::logs_bloom(self.logs.iter())
251 }
252
253 fn cumulative_gas_used(&self) -> u128 {
254 self.cumulative_gas_used as u128
255 }
256
257 fn logs(&self) -> &[Log] {
258 &self.logs
259 }
260}
261
262impl Typed2718 for Receipt {
263 fn ty(&self) -> u8 {
264 self.tx_type as u8
265 }
266}
267
268impl reth_primitives_traits::Receipt for Receipt {}
269
270impl InMemorySize for Receipt {
271 #[inline]
273 fn size(&self) -> usize {
274 let total_size = self.tx_type.size() +
275 core::mem::size_of::<bool>() +
276 core::mem::size_of::<u64>() +
277 self.logs.capacity() * core::mem::size_of::<Log>();
278
279 #[cfg(feature = "optimism")]
280 return total_size + 2 * core::mem::size_of::<Option<u64>>();
281 #[cfg(not(feature = "optimism"))]
282 total_size
283 }
284}
285
286#[derive(
288 Clone,
289 Debug,
290 PartialEq,
291 Eq,
292 Serialize,
293 Deserialize,
294 From,
295 derive_more::Deref,
296 DerefMut,
297 IntoIterator,
298)]
299pub struct Receipts<T = Receipt> {
300 pub receipt_vec: Vec<Vec<Option<T>>>,
302}
303
304impl<T> Receipts<T> {
305 pub fn len(&self) -> usize {
307 self.receipt_vec.len()
308 }
309
310 pub fn is_empty(&self) -> bool {
312 self.receipt_vec.is_empty()
313 }
314
315 pub fn push(&mut self, receipts: Vec<Option<T>>) {
317 self.receipt_vec.push(receipts);
318 }
319
320 pub fn root_slow(&self, index: usize, f: impl FnOnce(&[&T]) -> B256) -> Option<B256> {
322 let receipts =
323 self.receipt_vec[index].iter().map(Option::as_ref).collect::<Option<Vec<_>>>()?;
324 Some(f(receipts.as_slice()))
325 }
326}
327
328impl<T> From<Vec<T>> for Receipts<T> {
329 fn from(block_receipts: Vec<T>) -> Self {
330 Self { receipt_vec: vec![block_receipts.into_iter().map(Option::Some).collect()] }
331 }
332}
333
334impl<T> FromIterator<Vec<Option<T>>> for Receipts<T> {
335 fn from_iter<I: IntoIterator<Item = Vec<Option<T>>>>(iter: I) -> Self {
336 iter.into_iter().collect::<Vec<_>>().into()
337 }
338}
339
340impl<T> Default for Receipts<T> {
341 fn default() -> Self {
342 Self { receipt_vec: Vec::new() }
343 }
344}
345
346#[cfg(any(test, feature = "arbitrary"))]
347impl<'a> arbitrary::Arbitrary<'a> for Receipt {
348 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
349 let tx_type = TxType::arbitrary(u)?;
350 let success = bool::arbitrary(u)?;
351 let cumulative_gas_used = u64::arbitrary(u)?;
352 let logs = Vec::<Log>::arbitrary(u)?;
353
354 #[cfg(feature = "optimism")]
356 let (deposit_nonce, deposit_receipt_version) = if tx_type == TxType::Deposit {
357 let deposit_nonce = Option::<u64>::arbitrary(u)?;
358 let deposit_nonce_version =
359 deposit_nonce.map(|_| Option::<u64>::arbitrary(u)).transpose()?.flatten();
360 (deposit_nonce, deposit_nonce_version)
361 } else {
362 (None, None)
363 };
364
365 Ok(Self {
366 tx_type,
367 success,
368 cumulative_gas_used,
369 logs,
370 #[cfg(feature = "optimism")]
371 deposit_nonce,
372 #[cfg(feature = "optimism")]
373 deposit_receipt_version,
374 })
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381 use alloy_eips::eip2718::Encodable2718;
382 use alloy_primitives::{address, b256, bytes, hex_literal::hex, Bytes};
383 use reth_codecs::Compact;
384
385 #[test]
386 fn test_decode_receipt() {
387 #[cfg(not(feature = "optimism"))]
388 reth_codecs::test_utils::test_decode::<Receipt>(&hex!(
389 "c428b52ffd23fc42696156b10200f034792b6a94c3850215c2fef7aea361a0c31b79d9a32652eefc0d4e2e730036061cff7344b6fc6132b50cda0ed810a991ae58ef013150c12b2522533cb3b3a8b19b7786a8b5ff1d3cdc84225e22b02def168c8858df"
390 ));
391 #[cfg(feature = "optimism")]
392 reth_codecs::test_utils::test_decode::<Receipt>(&hex!(
393 "c30328b52ffd23fc426961a00105007eb0042307705a97e503562eacf2b95060cce9de6de68386b6c155b73a9650021a49e2f8baad17f30faff5899d785c4c0873e45bc268bcf07560106424570d11f9a59e8f3db1efa4ceec680123712275f10d92c3411e1caaa11c7c5d591bc11487168e09934a9986848136da1b583babf3a7188e3aed007a1520f1cf4c1ca7d3482c6c28d37c298613c70a76940008816c4c95644579fd08471dc34732fd0f24"
394 ));
395 }
396
397 #[test]
399 fn encode_legacy_receipt() {
400 let expected = hex!("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff");
401
402 let mut data = Vec::with_capacity(expected.length());
403 let receipt = ReceiptWithBloom {
404 receipt: Receipt {
405 tx_type: TxType::Legacy,
406 cumulative_gas_used: 0x1u64,
407 logs: vec![Log::new_unchecked(
408 address!("0000000000000000000000000000000000000011"),
409 vec![
410 b256!("000000000000000000000000000000000000000000000000000000000000dead"),
411 b256!("000000000000000000000000000000000000000000000000000000000000beef"),
412 ],
413 bytes!("0100ff"),
414 )],
415 success: false,
416 #[cfg(feature = "optimism")]
417 deposit_nonce: None,
418 #[cfg(feature = "optimism")]
419 deposit_receipt_version: None,
420 },
421 logs_bloom: [0; 256].into(),
422 };
423
424 receipt.encode(&mut data);
425
426 assert_eq!(receipt.length(), expected.len());
428 assert_eq!(data, expected);
429 }
430
431 #[test]
433 fn decode_legacy_receipt() {
434 let data = hex!("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff");
435
436 let expected = ReceiptWithBloom {
438 receipt: Receipt {
439 tx_type: TxType::Legacy,
440 cumulative_gas_used: 0x1u64,
441 logs: vec![Log::new_unchecked(
442 address!("0000000000000000000000000000000000000011"),
443 vec![
444 b256!("000000000000000000000000000000000000000000000000000000000000dead"),
445 b256!("000000000000000000000000000000000000000000000000000000000000beef"),
446 ],
447 bytes!("0100ff"),
448 )],
449 success: false,
450 #[cfg(feature = "optimism")]
451 deposit_nonce: None,
452 #[cfg(feature = "optimism")]
453 deposit_receipt_version: None,
454 },
455 logs_bloom: [0; 256].into(),
456 };
457
458 let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
459 assert_eq!(receipt, expected);
460 }
461
462 #[cfg(feature = "optimism")]
463 #[test]
464 fn decode_deposit_receipt_regolith_roundtrip() {
465 let data = hex!("b901107ef9010c0182b741b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0833d3bbf");
466
467 let expected = ReceiptWithBloom {
469 receipt: Receipt {
470 tx_type: TxType::Deposit,
471 cumulative_gas_used: 46913,
472 logs: vec![],
473 success: true,
474 deposit_nonce: Some(4012991),
475 deposit_receipt_version: None,
476 },
477 logs_bloom: [0; 256].into(),
478 };
479
480 let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
481 assert_eq!(receipt, expected);
482
483 let mut buf = Vec::with_capacity(data.len());
484 receipt.encode(&mut buf);
485 assert_eq!(buf, &data[..]);
486 }
487
488 #[cfg(feature = "optimism")]
489 #[test]
490 fn decode_deposit_receipt_canyon_roundtrip() {
491 let data = hex!("b901117ef9010d0182b741b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0833d3bbf01");
492
493 let expected = ReceiptWithBloom {
495 receipt: Receipt {
496 tx_type: TxType::Deposit,
497 cumulative_gas_used: 46913,
498 logs: vec![],
499 success: true,
500 deposit_nonce: Some(4012991),
501 deposit_receipt_version: Some(1),
502 },
503 logs_bloom: [0; 256].into(),
504 };
505
506 let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
507 assert_eq!(receipt, expected);
508
509 let mut buf = Vec::with_capacity(data.len());
510 expected.encode(&mut buf);
511 assert_eq!(buf, &data[..]);
512 }
513
514 #[test]
515 fn gigantic_receipt() {
516 let receipt = Receipt {
517 cumulative_gas_used: 16747627,
518 success: true,
519 tx_type: TxType::Legacy,
520 logs: vec![
521 Log::new_unchecked(
522 address!("4bf56695415f725e43c3e04354b604bcfb6dfb6e"),
523 vec![b256!("c69dc3d7ebff79e41f525be431d5cd3cc08f80eaf0f7819054a726eeb7086eb9")],
524 Bytes::from(vec![1; 0xffffff]),
525 ),
526 Log::new_unchecked(
527 address!("faca325c86bf9c2d5b413cd7b90b209be92229c2"),
528 vec![b256!("8cca58667b1e9ffa004720ac99a3d61a138181963b294d270d91c53d36402ae2")],
529 Bytes::from(vec![1; 0xffffff]),
530 ),
531 ],
532 #[cfg(feature = "optimism")]
533 deposit_nonce: None,
534 #[cfg(feature = "optimism")]
535 deposit_receipt_version: None,
536 };
537
538 let mut data = vec![];
539 receipt.to_compact(&mut data);
540 let (decoded, _) = Receipt::from_compact(&data[..], data.len());
541 assert_eq!(decoded, receipt);
542 }
543
544 #[test]
545 fn test_encode_2718_length() {
546 let receipt = ReceiptWithBloom {
547 receipt: Receipt {
548 tx_type: TxType::Eip1559,
549 success: true,
550 cumulative_gas_used: 21000,
551 logs: vec![],
552 #[cfg(feature = "optimism")]
553 deposit_nonce: None,
554 #[cfg(feature = "optimism")]
555 deposit_receipt_version: None,
556 },
557 logs_bloom: Bloom::default(),
558 };
559
560 let encoded = receipt.encoded_2718();
561 assert_eq!(
562 encoded.len(),
563 receipt.encode_2718_len(),
564 "Encoded length should match the actual encoded data length"
565 );
566
567 let legacy_receipt = ReceiptWithBloom {
569 receipt: Receipt {
570 tx_type: TxType::Legacy,
571 success: true,
572 cumulative_gas_used: 21000,
573 logs: vec![],
574 #[cfg(feature = "optimism")]
575 deposit_nonce: None,
576 #[cfg(feature = "optimism")]
577 deposit_receipt_version: None,
578 },
579 logs_bloom: Bloom::default(),
580 };
581
582 let legacy_encoded = legacy_receipt.encoded_2718();
583 assert_eq!(
584 legacy_encoded.len(),
585 legacy_receipt.encode_2718_len(),
586 "Encoded length for legacy receipt should match the actual encoded data length"
587 );
588 }
589}