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
44    /// [`ReceiptWithBloom`] can 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
50    /// [`ReceiptWithBloom`] container 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    /// Returns a reference to the bloom.
320    pub const fn bloom_ref(&self) -> &Bloom {
321        &self.logs_bloom
322    }
323}
324
325impl<L> ReceiptWithBloom<Receipt<L>> {
326    /// Converts the receipt's log type by applying a function to each log.
327    ///
328    /// Returns the receipt with the new log type.
329    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    /// Converts a [`ReceiptWithBloom`] with a custom log type into a [`ReceiptWithBloom`] with the
335    /// primitives [`Log`] type by converting the logs.
336    ///
337    /// This is useful if log types that embed the primitives log type, e.g. the log receipt rpc
338    /// type.
339    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    /// Bincode-compatible [`super::Receipt`] serde implementation.
399    ///
400    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
401    /// ```rust
402    /// use alloy_consensus::{serde_bincode_compat, Receipt};
403    /// use serde::{de::DeserializeOwned, Deserialize, Serialize};
404    /// use serde_with::serde_as;
405    ///
406    /// #[serde_as]
407    /// #[derive(Serialize, Deserialize)]
408    /// struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
409    ///     #[serde_as(as = "serde_bincode_compat::Receipt<'_, T>")]
410    ///     receipt: Receipt<T>,
411    /// }
412    /// ```
413    #[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                // OP has no post state root variant
425                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            // ensure we don't have an invalid poststate variant
484            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}