alloy_consensus/receipt/
receipts.rs

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/// Receipt containing result of transaction execution.
11#[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    /// If transaction is executed successfully.
18    ///
19    /// This is the `statusCode`
20    #[cfg_attr(feature = "serde", serde(flatten))]
21    pub status: Eip658Value,
22    /// Gas used
23    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
24    pub cumulative_gas_used: u64,
25    /// Log send from contracts.
26    pub logs: Vec<T>,
27}
28
29impl<T> Receipt<T> {
30    /// Converts the receipt's log type by applying a function to each log.
31    ///
32    /// Returns the receipt with the new log type
33    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    /// Calculates [`Log`]'s bloom filter. this is slow operation and [ReceiptWithBloom] can
44    /// be used to cache this value.
45    pub fn bloom_slow(&self) -> Bloom {
46        self.logs.iter().map(AsRef::as_ref).collect()
47    }
48
49    /// Calculates the bloom filter for the receipt and returns the [ReceiptWithBloom] container
50    /// type.
51    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    /// Converts a [`Receipt`] with a custom log type into a [`Receipt`] with the primitives [`Log`]
61    /// type by converting the logs.
62    ///
63    /// This is useful if log types that embed the primitives log type, e.g. the log receipt rpc
64    /// type.
65    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    /// Returns length of RLP-encoded receipt fields with the given [`Bloom`] without an RLP header.
99    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    /// RLP-encodes receipt fields with the given [`Bloom`] without an RLP header.
107    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    /// Returns RLP header for this receipt encoding with the given [`Bloom`].
115    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    /// RLP-decodes receipt's field with a [`Bloom`].
133    ///
134    /// Does not expect an RLP header.
135    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    /// Consume the structure, returning only the receipt
168    fn from(receipt_with_bloom: ReceiptWithBloom<Self>) -> Self {
169        receipt_with_bloom.receipt
170    }
171}
172
173/// A collection of receipts organized as a two-dimensional vector.
174#[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    /// A two-dimensional vector of [`Receipt`] instances.
187    pub receipt_vec: Vec<Vec<T>>,
188}
189
190impl<T> Receipts<T> {
191    /// Returns the length of the [`Receipts`] vector.
192    pub fn len(&self) -> usize {
193        self.receipt_vec.len()
194    }
195
196    /// Returns `true` if the [`Receipts`] vector is empty.
197    pub fn is_empty(&self) -> bool {
198        self.receipt_vec.is_empty()
199    }
200
201    /// Push a new vector of receipts into the [`Receipts`] collection.
202    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/// [`Receipt`] with calculated bloom filter.
242///
243/// This convenience type allows us to lazily calculate the bloom filter for a
244/// receipt, similar to [`Sealed`].
245///
246/// [`Sealed`]: crate::Sealed
247#[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    /// The receipt.
254    pub receipt: T,
255    /// The bloom filter.
256    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    /// Converts the receipt type by applying the given closure to it.
302    ///
303    /// Returns the type with the new receipt type.
304    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    /// Create new [ReceiptWithBloom]
310    pub const fn new(receipt: R, logs_bloom: Bloom) -> Self {
311        Self { receipt, logs_bloom }
312    }
313
314    /// Consume the structure, returning the receipt and the bloom filter
315    pub fn into_components(self) -> (R, Bloom) {
316        (self.receipt, self.logs_bloom)
317    }
318}
319
320impl<L> ReceiptWithBloom<Receipt<L>> {
321    /// Converts the receipt's log type by applying a function to each log.
322    ///
323    /// Returns the receipt with the new log type.
324    pub fn map_logs<U>(self, f: impl FnMut(L) -> U) -> ReceiptWithBloom<Receipt<U>> {
325        let Self { receipt, logs_bloom } = self;
326        ReceiptWithBloom { receipt: receipt.map_logs(f), logs_bloom }
327    }
328
329    /// Converts a [`ReceiptWithBloom`] with a custom log type into a [`ReceiptWithBloom`] with the
330    /// primitives [`Log`] type by converting the logs.
331    ///
332    /// This is useful if log types that embed the primitives log type, e.g. the log receipt rpc
333    /// type.
334    pub fn into_primitives_receipt(self) -> ReceiptWithBloom<Receipt<Log>>
335    where
336        L: Into<Log>,
337    {
338        self.map_logs(Into::into)
339    }
340}
341
342impl<R: RlpEncodableReceipt> Encodable for ReceiptWithBloom<R> {
343    fn encode(&self, out: &mut dyn BufMut) {
344        self.receipt.rlp_encode_with_bloom(&self.logs_bloom, out);
345    }
346
347    fn length(&self) -> usize {
348        self.receipt.rlp_encoded_length_with_bloom(&self.logs_bloom)
349    }
350}
351
352impl<R: RlpDecodableReceipt> Decodable for ReceiptWithBloom<R> {
353    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
354        R::rlp_decode_with_bloom(buf)
355    }
356}
357
358impl<R: Typed2718> Typed2718 for ReceiptWithBloom<R> {
359    fn ty(&self) -> u8 {
360        self.receipt.ty()
361    }
362}
363
364impl<R> Encodable2718 for ReceiptWithBloom<R>
365where
366    R: Eip2718EncodableReceipt + Send + Sync,
367{
368    fn encode_2718_len(&self) -> usize {
369        self.receipt.eip2718_encoded_length_with_bloom(&self.logs_bloom)
370    }
371
372    fn encode_2718(&self, out: &mut dyn BufMut) {
373        self.receipt.eip2718_encode_with_bloom(&self.logs_bloom, out);
374    }
375}
376
377#[cfg(any(test, feature = "arbitrary"))]
378impl<'a, R> arbitrary::Arbitrary<'a> for ReceiptWithBloom<R>
379where
380    R: arbitrary::Arbitrary<'a>,
381{
382    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
383        Ok(Self { receipt: R::arbitrary(u)?, logs_bloom: Bloom::arbitrary(u)? })
384    }
385}
386
387#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
388pub(crate) mod serde_bincode_compat {
389    use alloc::{borrow::Cow, vec::Vec};
390    use serde::{Deserialize, Deserializer, Serialize, Serializer};
391    use serde_with::{DeserializeAs, SerializeAs};
392
393    /// Bincode-compatible [`super::Receipt`] serde implementation.
394    ///
395    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
396    /// ```rust
397    /// use alloy_consensus::{serde_bincode_compat, Receipt};
398    /// use serde::{de::DeserializeOwned, Deserialize, Serialize};
399    /// use serde_with::serde_as;
400    ///
401    /// #[serde_as]
402    /// #[derive(Serialize, Deserialize)]
403    /// struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
404    ///     #[serde_as(as = "serde_bincode_compat::Receipt<'_, T>")]
405    ///     receipt: Receipt<T>,
406    /// }
407    /// ```
408    #[derive(Debug, Serialize, Deserialize)]
409    pub struct Receipt<'a, T: Clone> {
410        logs: Cow<'a, Vec<T>>,
411        status: bool,
412        cumulative_gas_used: u64,
413    }
414
415    impl<'a, T: Clone> From<&'a super::Receipt<T>> for Receipt<'a, T> {
416        fn from(value: &'a super::Receipt<T>) -> Self {
417            Self {
418                logs: Cow::Borrowed(&value.logs),
419                // OP has no post state root variant
420                status: value.status.coerce_status(),
421                cumulative_gas_used: value.cumulative_gas_used,
422            }
423        }
424    }
425
426    impl<'a, T: Clone> From<Receipt<'a, T>> for super::Receipt<T> {
427        fn from(value: Receipt<'a, T>) -> Self {
428            Self {
429                status: value.status.into(),
430                cumulative_gas_used: value.cumulative_gas_used,
431                logs: value.logs.into_owned(),
432            }
433        }
434    }
435
436    impl<T: Serialize + Clone> SerializeAs<super::Receipt<T>> for Receipt<'_, T> {
437        fn serialize_as<S>(source: &super::Receipt<T>, serializer: S) -> Result<S::Ok, S::Error>
438        where
439            S: Serializer,
440        {
441            Receipt::<'_, T>::from(source).serialize(serializer)
442        }
443    }
444
445    impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::Receipt<T>> for Receipt<'de, T> {
446        fn deserialize_as<D>(deserializer: D) -> Result<super::Receipt<T>, D::Error>
447        where
448            D: Deserializer<'de>,
449        {
450            Receipt::<'_, T>::deserialize(deserializer).map(Into::into)
451        }
452    }
453
454    #[cfg(test)]
455    mod tests {
456        use super::super::{serde_bincode_compat, Receipt};
457        use alloy_primitives::Log;
458        use arbitrary::Arbitrary;
459        use rand::Rng;
460        use serde::{de::DeserializeOwned, Deserialize, Serialize};
461        use serde_with::serde_as;
462
463        #[test]
464        fn test_receipt_bincode_roundtrip() {
465            #[serde_as]
466            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
467            struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
468                #[serde_as(as = "serde_bincode_compat::Receipt<'_,T>")]
469                receipt: Receipt<T>,
470            }
471
472            let mut bytes = [0u8; 1024];
473            rand::thread_rng().fill(bytes.as_mut_slice());
474            let mut data = Data {
475                receipt: Receipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(),
476            };
477            // ensure we don't have an invalid poststate variant
478            data.receipt.status = data.receipt.status.coerce_status().into();
479
480            let encoded = bincode::serialize(&data).unwrap();
481            let decoded: Data<Log> = bincode::deserialize(&encoded).unwrap();
482            assert_eq!(decoded, data);
483        }
484    }
485}
486
487#[cfg(test)]
488mod test {
489    use super::*;
490    use crate::ReceiptEnvelope;
491    use alloy_rlp::{Decodable, Encodable};
492
493    const fn assert_tx_receipt<T: TxReceipt>() {}
494
495    #[test]
496    const fn assert_receipt() {
497        assert_tx_receipt::<Receipt>();
498        assert_tx_receipt::<ReceiptWithBloom<Receipt>>();
499    }
500
501    #[cfg(feature = "serde")]
502    #[test]
503    fn root_vs_status() {
504        let receipt = super::Receipt::<()> {
505            status: super::Eip658Value::Eip658(true),
506            cumulative_gas_used: 0,
507            logs: Vec::new(),
508        };
509
510        let json = serde_json::to_string(&receipt).unwrap();
511        assert_eq!(json, r#"{"status":"0x1","cumulativeGasUsed":"0x0","logs":[]}"#);
512
513        let receipt = super::Receipt::<()> {
514            status: super::Eip658Value::PostState(Default::default()),
515            cumulative_gas_used: 0,
516            logs: Vec::new(),
517        };
518
519        let json = serde_json::to_string(&receipt).unwrap();
520        assert_eq!(
521            json,
522            r#"{"root":"0x0000000000000000000000000000000000000000000000000000000000000000","cumulativeGasUsed":"0x0","logs":[]}"#
523        );
524    }
525
526    #[cfg(feature = "serde")]
527    #[test]
528    fn deser_pre658() {
529        use alloy_primitives::b256;
530
531        let json = r#"{"root":"0x284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10","cumulativeGasUsed":"0x0","logs":[]}"#;
532
533        let receipt: super::Receipt<()> = serde_json::from_str(json).unwrap();
534
535        assert_eq!(
536            receipt.status,
537            super::Eip658Value::PostState(b256!(
538                "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
539            ))
540        );
541    }
542
543    #[test]
544    fn rountrip_encodable_eip1559() {
545        let receipts =
546            Receipts { receipt_vec: vec![vec![ReceiptEnvelope::Eip1559(Default::default())]] };
547
548        let mut out = vec![];
549        receipts.encode(&mut out);
550
551        let mut out = out.as_slice();
552        let decoded = Receipts::<ReceiptEnvelope>::decode(&mut out).unwrap();
553
554        assert_eq!(receipts, decoded);
555    }
556}