1use crate::receipt::{
2 Eip2718DecodableReceipt, Eip2718EncodableReceipt, Eip658Value, RlpDecodableReceipt,
3 RlpEncodableReceipt, TxReceipt,
4};
5use alloc::{vec, vec::Vec};
6use alloy_eips::{
7 eip2718::{Eip2718Result, Encodable2718},
8 Decodable2718, Typed2718,
9};
10use alloy_primitives::{Bloom, Log};
11use alloy_rlp::{BufMut, Decodable, Encodable, Header};
12use core::fmt;
13
14#[derive(Clone, Debug, Default, PartialEq, Eq)]
16#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
17#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
18#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
19#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
20#[doc(alias = "TransactionReceipt", alias = "TxReceipt")]
21pub struct Receipt<T = Log> {
22 #[cfg_attr(feature = "serde", serde(flatten))]
26 pub status: Eip658Value,
27 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
29 pub cumulative_gas_used: u64,
30 pub logs: Vec<T>,
32}
33
34impl<T> Receipt<T> {
35 pub fn map_logs<U>(self, f: impl FnMut(T) -> U) -> Receipt<U> {
39 let Self { status, cumulative_gas_used, logs } = self;
40 Receipt { status, cumulative_gas_used, logs: logs.into_iter().map(f).collect() }
41 }
42}
43
44impl<T> Receipt<T>
45where
46 T: AsRef<Log>,
47{
48 pub fn bloom_slow(&self) -> Bloom {
51 self.logs.iter().map(AsRef::as_ref).collect()
52 }
53
54 pub fn with_bloom(self) -> ReceiptWithBloom<Self> {
57 ReceiptWithBloom { logs_bloom: self.bloom_slow(), receipt: self }
58 }
59}
60
61impl<T> Receipt<T>
62where
63 T: Into<Log>,
64{
65 pub fn into_primitives_receipt(self) -> Receipt<Log> {
71 self.map_logs(Into::into)
72 }
73}
74
75impl<T> TxReceipt for Receipt<T>
76where
77 T: AsRef<Log> + Clone + fmt::Debug + PartialEq + Eq + Send + Sync,
78{
79 type Log = T;
80
81 fn status_or_post_state(&self) -> Eip658Value {
82 self.status
83 }
84
85 fn status(&self) -> bool {
86 self.status.coerce_status()
87 }
88
89 fn bloom(&self) -> Bloom {
90 self.bloom_slow()
91 }
92
93 fn cumulative_gas_used(&self) -> u64 {
94 self.cumulative_gas_used
95 }
96
97 fn logs(&self) -> &[Self::Log] {
98 &self.logs
99 }
100
101 fn into_logs(self) -> Vec<Self::Log>
102 where
103 Self::Log: Clone,
104 {
105 self.logs
106 }
107}
108
109impl<T: Encodable> Receipt<T> {
110 pub fn rlp_encoded_fields_length_with_bloom(&self, bloom: &Bloom) -> usize {
112 self.status.length()
113 + self.cumulative_gas_used.length()
114 + bloom.length()
115 + self.logs.length()
116 }
117
118 pub fn rlp_encode_fields_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
120 self.status.encode(out);
121 self.cumulative_gas_used.encode(out);
122 bloom.encode(out);
123 self.logs.encode(out);
124 }
125
126 pub fn rlp_header_with_bloom(&self, bloom: &Bloom) -> Header {
128 Header { list: true, payload_length: self.rlp_encoded_fields_length_with_bloom(bloom) }
129 }
130}
131
132impl<T: Encodable> RlpEncodableReceipt for Receipt<T> {
133 fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
134 self.rlp_header_with_bloom(bloom).length_with_payload()
135 }
136
137 fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
138 self.rlp_header_with_bloom(bloom).encode(out);
139 self.rlp_encode_fields_with_bloom(bloom, out);
140 }
141}
142
143impl<T: Decodable> Receipt<T> {
144 pub fn rlp_decode_fields_with_bloom(
148 buf: &mut &[u8],
149 ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
150 let status = Decodable::decode(buf)?;
151 let cumulative_gas_used = Decodable::decode(buf)?;
152 let logs_bloom = Decodable::decode(buf)?;
153 let logs = Decodable::decode(buf)?;
154
155 Ok(ReceiptWithBloom { receipt: Self { status, cumulative_gas_used, logs }, logs_bloom })
156 }
157}
158
159impl<T: Decodable> RlpDecodableReceipt for Receipt<T> {
160 fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
161 let header = Header::decode(buf)?;
162 if !header.list {
163 return Err(alloy_rlp::Error::UnexpectedString);
164 }
165
166 let remaining = buf.len();
167
168 let this = Self::rlp_decode_fields_with_bloom(buf)?;
169
170 if buf.len() + header.payload_length != remaining {
171 return Err(alloy_rlp::Error::UnexpectedLength);
172 }
173
174 Ok(this)
175 }
176}
177
178impl<T> From<ReceiptWithBloom<Self>> for Receipt<T> {
179 fn from(receipt_with_bloom: ReceiptWithBloom<Self>) -> Self {
181 receipt_with_bloom.receipt
182 }
183}
184
185#[derive(
187 Clone,
188 Debug,
189 PartialEq,
190 Eq,
191 derive_more::Deref,
192 derive_more::DerefMut,
193 derive_more::From,
194 derive_more::IntoIterator,
195)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
197pub struct Receipts<T> {
198 pub receipt_vec: Vec<Vec<T>>,
200}
201
202impl<T> Receipts<T> {
203 pub const fn len(&self) -> usize {
205 self.receipt_vec.len()
206 }
207
208 pub const fn is_empty(&self) -> bool {
210 self.receipt_vec.is_empty()
211 }
212
213 pub fn push(&mut self, receipts: Vec<T>) {
215 self.receipt_vec.push(receipts);
216 }
217}
218
219impl<T> From<Vec<T>> for Receipts<T> {
220 fn from(block_receipts: Vec<T>) -> Self {
221 Self { receipt_vec: vec![block_receipts] }
222 }
223}
224
225impl<T> FromIterator<Vec<T>> for Receipts<T> {
226 fn from_iter<I: IntoIterator<Item = Vec<T>>>(iter: I) -> Self {
227 Self { receipt_vec: iter.into_iter().collect() }
228 }
229}
230
231impl<T: Encodable> Encodable for Receipts<T> {
232 fn encode(&self, out: &mut dyn BufMut) {
233 self.receipt_vec.encode(out)
234 }
235
236 fn length(&self) -> usize {
237 self.receipt_vec.length()
238 }
239}
240
241impl<T: Decodable> Decodable for Receipts<T> {
242 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
243 Ok(Self { receipt_vec: Decodable::decode(buf)? })
244 }
245}
246
247impl<T> Default for Receipts<T> {
248 fn default() -> Self {
249 Self { receipt_vec: Default::default() }
250 }
251}
252
253#[derive(Clone, Debug, Default, PartialEq, Eq)]
260#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
261#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
262#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
263#[doc(alias = "TransactionReceiptWithBloom", alias = "TxReceiptWithBloom")]
264pub struct ReceiptWithBloom<T = Receipt<Log>> {
265 #[cfg_attr(feature = "serde", serde(flatten))]
266 pub receipt: T,
268 pub logs_bloom: Bloom,
270}
271
272impl<R> TxReceipt for ReceiptWithBloom<R>
273where
274 R: TxReceipt,
275{
276 type Log = R::Log;
277
278 fn status_or_post_state(&self) -> Eip658Value {
279 self.receipt.status_or_post_state()
280 }
281
282 fn status(&self) -> bool {
283 self.receipt.status()
284 }
285
286 fn bloom(&self) -> Bloom {
287 self.logs_bloom
288 }
289
290 fn bloom_cheap(&self) -> Option<Bloom> {
291 Some(self.logs_bloom)
292 }
293
294 fn cumulative_gas_used(&self) -> u64 {
295 self.receipt.cumulative_gas_used()
296 }
297
298 fn logs(&self) -> &[Self::Log] {
299 self.receipt.logs()
300 }
301
302 fn into_logs(self) -> Vec<Self::Log>
303 where
304 Self::Log: Clone,
305 {
306 self.receipt.into_logs()
307 }
308}
309
310impl<R> From<R> for ReceiptWithBloom<R>
311where
312 R: TxReceipt,
313{
314 fn from(receipt: R) -> Self {
315 let logs_bloom = receipt.bloom();
316 Self { logs_bloom, receipt }
317 }
318}
319
320impl<R> ReceiptWithBloom<R> {
321 pub fn map_receipt<U>(self, f: impl FnOnce(R) -> U) -> ReceiptWithBloom<U> {
325 let Self { receipt, logs_bloom } = self;
326 ReceiptWithBloom { receipt: f(receipt), logs_bloom }
327 }
328
329 pub const fn new(receipt: R, logs_bloom: Bloom) -> Self {
331 Self { receipt, logs_bloom }
332 }
333
334 pub fn into_components(self) -> (R, Bloom) {
336 (self.receipt, self.logs_bloom)
337 }
338
339 pub const fn bloom_ref(&self) -> &Bloom {
341 &self.logs_bloom
342 }
343}
344
345impl<L> ReceiptWithBloom<Receipt<L>> {
346 pub fn map_logs<U>(self, f: impl FnMut(L) -> U) -> ReceiptWithBloom<Receipt<U>> {
350 let Self { receipt, logs_bloom } = self;
351 ReceiptWithBloom { receipt: receipt.map_logs(f), logs_bloom }
352 }
353
354 pub fn into_primitives_receipt(self) -> ReceiptWithBloom<Receipt<Log>>
360 where
361 L: Into<Log>,
362 {
363 self.map_logs(Into::into)
364 }
365}
366
367impl<R: RlpEncodableReceipt> Encodable for ReceiptWithBloom<R> {
368 fn encode(&self, out: &mut dyn BufMut) {
369 self.receipt.rlp_encode_with_bloom(&self.logs_bloom, out);
370 }
371
372 fn length(&self) -> usize {
373 self.receipt.rlp_encoded_length_with_bloom(&self.logs_bloom)
374 }
375}
376
377impl<R: RlpDecodableReceipt> Decodable for ReceiptWithBloom<R> {
378 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
379 R::rlp_decode_with_bloom(buf)
380 }
381}
382
383impl<R: Typed2718> Typed2718 for ReceiptWithBloom<R> {
384 fn ty(&self) -> u8 {
385 self.receipt.ty()
386 }
387}
388
389impl<R> Encodable2718 for ReceiptWithBloom<R>
390where
391 R: Eip2718EncodableReceipt + Send + Sync,
392{
393 fn encode_2718_len(&self) -> usize {
394 self.receipt.eip2718_encoded_length_with_bloom(&self.logs_bloom)
395 }
396
397 fn encode_2718(&self, out: &mut dyn BufMut) {
398 self.receipt.eip2718_encode_with_bloom(&self.logs_bloom, out);
399 }
400}
401
402impl<R> Decodable2718 for ReceiptWithBloom<R>
403where
404 R: Eip2718DecodableReceipt,
405{
406 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
407 R::typed_decode_with_bloom(ty, buf)
408 }
409
410 fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
411 R::fallback_decode_with_bloom(buf)
412 }
413}
414
415#[cfg(any(test, feature = "arbitrary"))]
416impl<'a, R> arbitrary::Arbitrary<'a> for ReceiptWithBloom<R>
417where
418 R: arbitrary::Arbitrary<'a>,
419{
420 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
421 Ok(Self { receipt: R::arbitrary(u)?, logs_bloom: Bloom::arbitrary(u)? })
422 }
423}
424
425#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
426pub(crate) mod serde_bincode_compat {
427 use alloc::borrow::Cow;
428 use serde::{Deserialize, Deserializer, Serialize, Serializer};
429 use serde_with::{DeserializeAs, SerializeAs};
430
431 #[derive(Debug, Serialize, Deserialize)]
447 pub struct Receipt<'a, T: Clone = alloy_primitives::Log> {
448 logs: Cow<'a, [T]>,
449 status: bool,
450 cumulative_gas_used: u64,
451 }
452
453 impl<'a, T: Clone> From<&'a super::Receipt<T>> for Receipt<'a, T> {
454 fn from(value: &'a super::Receipt<T>) -> Self {
455 Self {
456 logs: Cow::Borrowed(&value.logs),
457 status: value.status.coerce_status(),
459 cumulative_gas_used: value.cumulative_gas_used,
460 }
461 }
462 }
463
464 impl<'a, T: Clone> From<Receipt<'a, T>> for super::Receipt<T> {
465 fn from(value: Receipt<'a, T>) -> Self {
466 Self {
467 status: value.status.into(),
468 cumulative_gas_used: value.cumulative_gas_used,
469 logs: value.logs.into_owned(),
470 }
471 }
472 }
473
474 impl<T: Serialize + Clone> SerializeAs<super::Receipt<T>> for Receipt<'_, T> {
475 fn serialize_as<S>(source: &super::Receipt<T>, serializer: S) -> Result<S::Ok, S::Error>
476 where
477 S: Serializer,
478 {
479 Receipt::<'_, T>::from(source).serialize(serializer)
480 }
481 }
482
483 impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::Receipt<T>> for Receipt<'de, T> {
484 fn deserialize_as<D>(deserializer: D) -> Result<super::Receipt<T>, D::Error>
485 where
486 D: Deserializer<'de>,
487 {
488 Receipt::<'_, T>::deserialize(deserializer).map(Into::into)
489 }
490 }
491
492 #[cfg(test)]
493 mod tests {
494 use super::super::{serde_bincode_compat, Receipt};
495 use alloy_primitives::Log;
496 use arbitrary::Arbitrary;
497 use bincode::config;
498 use rand::Rng;
499 use serde::{de::DeserializeOwned, Deserialize, Serialize};
500 use serde_with::serde_as;
501
502 #[test]
503 fn test_receipt_bincode_roundtrip() {
504 #[serde_as]
505 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
506 struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
507 #[serde_as(as = "serde_bincode_compat::Receipt<'_,T>")]
508 receipt: Receipt<T>,
509 }
510
511 let mut bytes = [0u8; 1024];
512 rand::thread_rng().fill(bytes.as_mut_slice());
513 let mut data = Data {
514 receipt: Receipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(),
515 };
516 data.receipt.status = data.receipt.status.coerce_status().into();
518
519 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
520 let (decoded, _) =
521 bincode::serde::decode_from_slice::<Data<Log>, _>(&encoded, config::legacy())
522 .unwrap();
523 assert_eq!(decoded, data);
524 }
525 }
526}
527
528#[cfg(test)]
529mod test {
530 use super::*;
531 use crate::ReceiptEnvelope;
532 use alloy_rlp::{Decodable, Encodable};
533
534 const fn assert_tx_receipt<T: TxReceipt>() {}
535
536 #[test]
537 const fn assert_receipt() {
538 assert_tx_receipt::<Receipt>();
539 assert_tx_receipt::<ReceiptWithBloom<Receipt>>();
540 }
541
542 #[cfg(feature = "serde")]
543 #[test]
544 fn root_vs_status() {
545 let receipt = super::Receipt::<()> {
546 status: super::Eip658Value::Eip658(true),
547 cumulative_gas_used: 0,
548 logs: Vec::new(),
549 };
550
551 let json = serde_json::to_string(&receipt).unwrap();
552 assert_eq!(json, r#"{"status":"0x1","cumulativeGasUsed":"0x0","logs":[]}"#);
553
554 let receipt = super::Receipt::<()> {
555 status: super::Eip658Value::PostState(Default::default()),
556 cumulative_gas_used: 0,
557 logs: Vec::new(),
558 };
559
560 let json = serde_json::to_string(&receipt).unwrap();
561 assert_eq!(
562 json,
563 r#"{"root":"0x0000000000000000000000000000000000000000000000000000000000000000","cumulativeGasUsed":"0x0","logs":[]}"#
564 );
565 }
566
567 #[cfg(feature = "serde")]
568 #[test]
569 fn deser_pre658() {
570 use alloy_primitives::b256;
571
572 let json = r#"{"root":"0x284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10","cumulativeGasUsed":"0x0","logs":[]}"#;
573
574 let receipt: super::Receipt<()> = serde_json::from_str(json).unwrap();
575
576 assert_eq!(
577 receipt.status,
578 super::Eip658Value::PostState(b256!(
579 "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
580 ))
581 );
582 }
583
584 #[test]
585 fn roundtrip_encodable_eip1559() {
586 let receipts =
587 Receipts { receipt_vec: vec![vec![ReceiptEnvelope::Eip1559(Default::default())]] };
588
589 let mut out = vec![];
590 receipts.encode(&mut out);
591
592 let mut out = out.as_slice();
593 let decoded = Receipts::<ReceiptEnvelope>::decode(&mut out).unwrap();
594
595 assert_eq!(receipts, decoded);
596 }
597}