1use crate::receipt::{
2 Eip2718EncodableReceipt, Eip658Value, RlpDecodableReceipt, RlpEncodableReceipt, TxReceipt,
3};
4use alloc::{vec, vec::Vec};
5use alloy_eips::{eip2718::Encodable2718, Typed2718};
6use alloy_primitives::{Bloom, Log};
7use alloy_rlp::{BufMut, Decodable, Encodable, Header};
8use core::fmt;
9
10#[derive(Clone, Debug, Default, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
13#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
14#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
15#[doc(alias = "TransactionReceipt", alias = "TxReceipt")]
16pub struct Receipt<T = Log> {
17 #[cfg_attr(feature = "serde", serde(flatten))]
21 pub status: Eip658Value,
22 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
24 pub cumulative_gas_used: u64,
25 pub logs: Vec<T>,
27}
28
29impl<T> Receipt<T> {
30 pub fn map_logs<U>(self, f: impl FnMut(T) -> U) -> Receipt<U> {
34 let Self { status, cumulative_gas_used, logs } = self;
35 Receipt { status, cumulative_gas_used, logs: logs.into_iter().map(f).collect() }
36 }
37}
38
39impl<T> Receipt<T>
40where
41 T: AsRef<Log>,
42{
43 pub fn bloom_slow(&self) -> Bloom {
46 self.logs.iter().map(AsRef::as_ref).collect()
47 }
48
49 pub fn with_bloom(self) -> ReceiptWithBloom<Self> {
52 ReceiptWithBloom { logs_bloom: self.bloom_slow(), receipt: self }
53 }
54}
55
56impl<T> Receipt<T>
57where
58 T: Into<Log>,
59{
60 pub fn into_primitives_receipt(self) -> Receipt<Log> {
66 self.map_logs(Into::into)
67 }
68}
69
70impl<T> TxReceipt for Receipt<T>
71where
72 T: AsRef<Log> + Clone + fmt::Debug + PartialEq + Eq + Send + Sync,
73{
74 type Log = T;
75
76 fn status_or_post_state(&self) -> Eip658Value {
77 self.status
78 }
79
80 fn status(&self) -> bool {
81 self.status.coerce_status()
82 }
83
84 fn bloom(&self) -> Bloom {
85 self.bloom_slow()
86 }
87
88 fn cumulative_gas_used(&self) -> u64 {
89 self.cumulative_gas_used
90 }
91
92 fn logs(&self) -> &[Self::Log] {
93 &self.logs
94 }
95}
96
97impl<T: Encodable> Receipt<T> {
98 pub fn rlp_encoded_fields_length_with_bloom(&self, bloom: &Bloom) -> usize {
100 self.status.length()
101 + self.cumulative_gas_used.length()
102 + bloom.length()
103 + self.logs.length()
104 }
105
106 pub fn rlp_encode_fields_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
108 self.status.encode(out);
109 self.cumulative_gas_used.encode(out);
110 bloom.encode(out);
111 self.logs.encode(out);
112 }
113
114 pub fn rlp_header_with_bloom(&self, bloom: &Bloom) -> Header {
116 Header { list: true, payload_length: self.rlp_encoded_fields_length_with_bloom(bloom) }
117 }
118}
119
120impl<T: Encodable> RlpEncodableReceipt for Receipt<T> {
121 fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
122 self.rlp_header_with_bloom(bloom).length_with_payload()
123 }
124
125 fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
126 self.rlp_header_with_bloom(bloom).encode(out);
127 self.rlp_encode_fields_with_bloom(bloom, out);
128 }
129}
130
131impl<T: Decodable> Receipt<T> {
132 pub fn rlp_decode_fields_with_bloom(
136 buf: &mut &[u8],
137 ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
138 let status = Decodable::decode(buf)?;
139 let cumulative_gas_used = Decodable::decode(buf)?;
140 let logs_bloom = Decodable::decode(buf)?;
141 let logs = Decodable::decode(buf)?;
142
143 Ok(ReceiptWithBloom { receipt: Self { status, cumulative_gas_used, logs }, logs_bloom })
144 }
145}
146
147impl<T: Decodable> RlpDecodableReceipt for Receipt<T> {
148 fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
149 let header = Header::decode(buf)?;
150 if !header.list {
151 return Err(alloy_rlp::Error::UnexpectedString);
152 }
153
154 let remaining = buf.len();
155
156 let this = Self::rlp_decode_fields_with_bloom(buf)?;
157
158 if buf.len() + header.payload_length != remaining {
159 return Err(alloy_rlp::Error::UnexpectedLength);
160 }
161
162 Ok(this)
163 }
164}
165
166impl<T> From<ReceiptWithBloom<Self>> for Receipt<T> {
167 fn from(receipt_with_bloom: ReceiptWithBloom<Self>) -> Self {
169 receipt_with_bloom.receipt
170 }
171}
172
173#[derive(
175 Clone,
176 Debug,
177 PartialEq,
178 Eq,
179 derive_more::Deref,
180 derive_more::DerefMut,
181 derive_more::From,
182 derive_more::IntoIterator,
183)]
184#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
185pub struct Receipts<T> {
186 pub receipt_vec: Vec<Vec<T>>,
188}
189
190impl<T> Receipts<T> {
191 pub fn len(&self) -> usize {
193 self.receipt_vec.len()
194 }
195
196 pub fn is_empty(&self) -> bool {
198 self.receipt_vec.is_empty()
199 }
200
201 pub fn push(&mut self, receipts: Vec<T>) {
203 self.receipt_vec.push(receipts);
204 }
205}
206
207impl<T> From<Vec<T>> for Receipts<T> {
208 fn from(block_receipts: Vec<T>) -> Self {
209 Self { receipt_vec: vec![block_receipts] }
210 }
211}
212
213impl<T> FromIterator<Vec<T>> for Receipts<T> {
214 fn from_iter<I: IntoIterator<Item = Vec<T>>>(iter: I) -> Self {
215 Self { receipt_vec: iter.into_iter().collect() }
216 }
217}
218
219impl<T: Encodable> Encodable for Receipts<T> {
220 fn encode(&self, out: &mut dyn BufMut) {
221 self.receipt_vec.encode(out)
222 }
223
224 fn length(&self) -> usize {
225 self.receipt_vec.length()
226 }
227}
228
229impl<T: Decodable> Decodable for Receipts<T> {
230 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
231 Ok(Self { receipt_vec: Decodable::decode(buf)? })
232 }
233}
234
235impl<T> Default for Receipts<T> {
236 fn default() -> Self {
237 Self { receipt_vec: Default::default() }
238 }
239}
240
241#[derive(Clone, Debug, Default, PartialEq, Eq)]
248#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
249#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
250#[doc(alias = "TransactionReceiptWithBloom", alias = "TxReceiptWithBloom")]
251pub struct ReceiptWithBloom<T = Receipt<Log>> {
252 #[cfg_attr(feature = "serde", serde(flatten))]
253 pub receipt: T,
255 pub logs_bloom: Bloom,
257}
258
259impl<R> TxReceipt for ReceiptWithBloom<R>
260where
261 R: TxReceipt,
262{
263 type Log = R::Log;
264
265 fn status_or_post_state(&self) -> Eip658Value {
266 self.receipt.status_or_post_state()
267 }
268
269 fn status(&self) -> bool {
270 self.receipt.status()
271 }
272
273 fn bloom(&self) -> Bloom {
274 self.logs_bloom
275 }
276
277 fn bloom_cheap(&self) -> Option<Bloom> {
278 Some(self.logs_bloom)
279 }
280
281 fn cumulative_gas_used(&self) -> u64 {
282 self.receipt.cumulative_gas_used()
283 }
284
285 fn logs(&self) -> &[Self::Log] {
286 self.receipt.logs()
287 }
288}
289
290impl<R> From<R> for ReceiptWithBloom<R>
291where
292 R: TxReceipt,
293{
294 fn from(receipt: R) -> Self {
295 let logs_bloom = receipt.bloom();
296 Self { logs_bloom, receipt }
297 }
298}
299
300impl<R> ReceiptWithBloom<R> {
301 pub fn map_receipt<U>(self, f: impl FnOnce(R) -> U) -> ReceiptWithBloom<U> {
305 let Self { receipt, logs_bloom } = self;
306 ReceiptWithBloom { receipt: f(receipt), logs_bloom }
307 }
308
309 pub const fn new(receipt: R, logs_bloom: Bloom) -> Self {
311 Self { receipt, logs_bloom }
312 }
313
314 pub fn into_components(self) -> (R, Bloom) {
316 (self.receipt, self.logs_bloom)
317 }
318
319 pub const fn bloom_ref(&self) -> &Bloom {
321 &self.logs_bloom
322 }
323}
324
325impl<L> ReceiptWithBloom<Receipt<L>> {
326 pub fn map_logs<U>(self, f: impl FnMut(L) -> U) -> ReceiptWithBloom<Receipt<U>> {
330 let Self { receipt, logs_bloom } = self;
331 ReceiptWithBloom { receipt: receipt.map_logs(f), logs_bloom }
332 }
333
334 pub fn into_primitives_receipt(self) -> ReceiptWithBloom<Receipt<Log>>
340 where
341 L: Into<Log>,
342 {
343 self.map_logs(Into::into)
344 }
345}
346
347impl<R: RlpEncodableReceipt> Encodable for ReceiptWithBloom<R> {
348 fn encode(&self, out: &mut dyn BufMut) {
349 self.receipt.rlp_encode_with_bloom(&self.logs_bloom, out);
350 }
351
352 fn length(&self) -> usize {
353 self.receipt.rlp_encoded_length_with_bloom(&self.logs_bloom)
354 }
355}
356
357impl<R: RlpDecodableReceipt> Decodable for ReceiptWithBloom<R> {
358 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
359 R::rlp_decode_with_bloom(buf)
360 }
361}
362
363impl<R: Typed2718> Typed2718 for ReceiptWithBloom<R> {
364 fn ty(&self) -> u8 {
365 self.receipt.ty()
366 }
367}
368
369impl<R> Encodable2718 for ReceiptWithBloom<R>
370where
371 R: Eip2718EncodableReceipt + Send + Sync,
372{
373 fn encode_2718_len(&self) -> usize {
374 self.receipt.eip2718_encoded_length_with_bloom(&self.logs_bloom)
375 }
376
377 fn encode_2718(&self, out: &mut dyn BufMut) {
378 self.receipt.eip2718_encode_with_bloom(&self.logs_bloom, out);
379 }
380}
381
382#[cfg(any(test, feature = "arbitrary"))]
383impl<'a, R> arbitrary::Arbitrary<'a> for ReceiptWithBloom<R>
384where
385 R: arbitrary::Arbitrary<'a>,
386{
387 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
388 Ok(Self { receipt: R::arbitrary(u)?, logs_bloom: Bloom::arbitrary(u)? })
389 }
390}
391
392#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
393pub(crate) mod serde_bincode_compat {
394 use alloc::borrow::Cow;
395 use serde::{Deserialize, Deserializer, Serialize, Serializer};
396 use serde_with::{DeserializeAs, SerializeAs};
397
398 #[derive(Debug, Serialize, Deserialize)]
414 pub struct Receipt<'a, T: Clone = alloy_primitives::Log> {
415 logs: Cow<'a, [T]>,
416 status: bool,
417 cumulative_gas_used: u64,
418 }
419
420 impl<'a, T: Clone> From<&'a super::Receipt<T>> for Receipt<'a, T> {
421 fn from(value: &'a super::Receipt<T>) -> Self {
422 Self {
423 logs: Cow::Borrowed(&value.logs),
424 status: value.status.coerce_status(),
426 cumulative_gas_used: value.cumulative_gas_used,
427 }
428 }
429 }
430
431 impl<'a, T: Clone> From<Receipt<'a, T>> for super::Receipt<T> {
432 fn from(value: Receipt<'a, T>) -> Self {
433 Self {
434 status: value.status.into(),
435 cumulative_gas_used: value.cumulative_gas_used,
436 logs: value.logs.into_owned(),
437 }
438 }
439 }
440
441 impl<T: Serialize + Clone> SerializeAs<super::Receipt<T>> for Receipt<'_, T> {
442 fn serialize_as<S>(source: &super::Receipt<T>, serializer: S) -> Result<S::Ok, S::Error>
443 where
444 S: Serializer,
445 {
446 Receipt::<'_, T>::from(source).serialize(serializer)
447 }
448 }
449
450 impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::Receipt<T>> for Receipt<'de, T> {
451 fn deserialize_as<D>(deserializer: D) -> Result<super::Receipt<T>, D::Error>
452 where
453 D: Deserializer<'de>,
454 {
455 Receipt::<'_, T>::deserialize(deserializer).map(Into::into)
456 }
457 }
458
459 #[cfg(test)]
460 mod tests {
461 use super::super::{serde_bincode_compat, Receipt};
462 use alloy_primitives::Log;
463 use arbitrary::Arbitrary;
464 use bincode::config;
465 use rand::Rng;
466 use serde::{de::DeserializeOwned, Deserialize, Serialize};
467 use serde_with::serde_as;
468
469 #[test]
470 fn test_receipt_bincode_roundtrip() {
471 #[serde_as]
472 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
473 struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
474 #[serde_as(as = "serde_bincode_compat::Receipt<'_,T>")]
475 receipt: Receipt<T>,
476 }
477
478 let mut bytes = [0u8; 1024];
479 rand::thread_rng().fill(bytes.as_mut_slice());
480 let mut data = Data {
481 receipt: Receipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(),
482 };
483 data.receipt.status = data.receipt.status.coerce_status().into();
485
486 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
487 let (decoded, _) =
488 bincode::serde::decode_from_slice::<Data<Log>, _>(&encoded, config::legacy())
489 .unwrap();
490 assert_eq!(decoded, data);
491 }
492 }
493}
494
495#[cfg(test)]
496mod test {
497 use super::*;
498 use crate::ReceiptEnvelope;
499 use alloy_rlp::{Decodable, Encodable};
500
501 const fn assert_tx_receipt<T: TxReceipt>() {}
502
503 #[test]
504 const fn assert_receipt() {
505 assert_tx_receipt::<Receipt>();
506 assert_tx_receipt::<ReceiptWithBloom<Receipt>>();
507 }
508
509 #[cfg(feature = "serde")]
510 #[test]
511 fn root_vs_status() {
512 let receipt = super::Receipt::<()> {
513 status: super::Eip658Value::Eip658(true),
514 cumulative_gas_used: 0,
515 logs: Vec::new(),
516 };
517
518 let json = serde_json::to_string(&receipt).unwrap();
519 assert_eq!(json, r#"{"status":"0x1","cumulativeGasUsed":"0x0","logs":[]}"#);
520
521 let receipt = super::Receipt::<()> {
522 status: super::Eip658Value::PostState(Default::default()),
523 cumulative_gas_used: 0,
524 logs: Vec::new(),
525 };
526
527 let json = serde_json::to_string(&receipt).unwrap();
528 assert_eq!(
529 json,
530 r#"{"root":"0x0000000000000000000000000000000000000000000000000000000000000000","cumulativeGasUsed":"0x0","logs":[]}"#
531 );
532 }
533
534 #[cfg(feature = "serde")]
535 #[test]
536 fn deser_pre658() {
537 use alloy_primitives::b256;
538
539 let json = r#"{"root":"0x284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10","cumulativeGasUsed":"0x0","logs":[]}"#;
540
541 let receipt: super::Receipt<()> = serde_json::from_str(json).unwrap();
542
543 assert_eq!(
544 receipt.status,
545 super::Eip658Value::PostState(b256!(
546 "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
547 ))
548 );
549 }
550
551 #[test]
552 fn rountrip_encodable_eip1559() {
553 let receipts =
554 Receipts { receipt_vec: vec![vec![ReceiptEnvelope::Eip1559(Default::default())]] };
555
556 let mut out = vec![];
557 receipts.encode(&mut out);
558
559 let mut out = out.as_slice();
560 let decoded = Receipts::<ReceiptEnvelope>::decode(&mut out).unwrap();
561
562 assert_eq!(receipts, decoded);
563 }
564}