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    fn into_logs(self) -> Vec<Self::Log>
97    where
98        Self::Log: Clone,
99    {
100        self.logs
101    }
102}
103
104impl<T: Encodable> Receipt<T> {
105    /// Returns length of RLP-encoded receipt fields with the given [`Bloom`] without an RLP header.
106    pub fn rlp_encoded_fields_length_with_bloom(&self, bloom: &Bloom) -> usize {
107        self.status.length()
108            + self.cumulative_gas_used.length()
109            + bloom.length()
110            + self.logs.length()
111    }
112
113    /// RLP-encodes receipt fields with the given [`Bloom`] without an RLP header.
114    pub fn rlp_encode_fields_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
115        self.status.encode(out);
116        self.cumulative_gas_used.encode(out);
117        bloom.encode(out);
118        self.logs.encode(out);
119    }
120
121    /// Returns RLP header for this receipt encoding with the given [`Bloom`].
122    pub fn rlp_header_with_bloom(&self, bloom: &Bloom) -> Header {
123        Header { list: true, payload_length: self.rlp_encoded_fields_length_with_bloom(bloom) }
124    }
125}
126
127impl<T: Encodable> RlpEncodableReceipt for Receipt<T> {
128    fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
129        self.rlp_header_with_bloom(bloom).length_with_payload()
130    }
131
132    fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
133        self.rlp_header_with_bloom(bloom).encode(out);
134        self.rlp_encode_fields_with_bloom(bloom, out);
135    }
136}
137
138impl<T: Decodable> Receipt<T> {
139    /// RLP-decodes receipt's field with a [`Bloom`].
140    ///
141    /// Does not expect an RLP header.
142    pub fn rlp_decode_fields_with_bloom(
143        buf: &mut &[u8],
144    ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
145        let status = Decodable::decode(buf)?;
146        let cumulative_gas_used = Decodable::decode(buf)?;
147        let logs_bloom = Decodable::decode(buf)?;
148        let logs = Decodable::decode(buf)?;
149
150        Ok(ReceiptWithBloom { receipt: Self { status, cumulative_gas_used, logs }, logs_bloom })
151    }
152}
153
154impl<T: Decodable> RlpDecodableReceipt for Receipt<T> {
155    fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
156        let header = Header::decode(buf)?;
157        if !header.list {
158            return Err(alloy_rlp::Error::UnexpectedString);
159        }
160
161        let remaining = buf.len();
162
163        let this = Self::rlp_decode_fields_with_bloom(buf)?;
164
165        if buf.len() + header.payload_length != remaining {
166            return Err(alloy_rlp::Error::UnexpectedLength);
167        }
168
169        Ok(this)
170    }
171}
172
173impl<T> From<ReceiptWithBloom<Self>> for Receipt<T> {
174    /// Consume the structure, returning only the receipt
175    fn from(receipt_with_bloom: ReceiptWithBloom<Self>) -> Self {
176        receipt_with_bloom.receipt
177    }
178}
179
180/// A collection of receipts organized as a two-dimensional vector.
181#[derive(
182    Clone,
183    Debug,
184    PartialEq,
185    Eq,
186    derive_more::Deref,
187    derive_more::DerefMut,
188    derive_more::From,
189    derive_more::IntoIterator,
190)]
191#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
192pub struct Receipts<T> {
193    /// A two-dimensional vector of [`Receipt`] instances.
194    pub receipt_vec: Vec<Vec<T>>,
195}
196
197impl<T> Receipts<T> {
198    /// Returns the length of the [`Receipts`] vector.
199    pub fn len(&self) -> usize {
200        self.receipt_vec.len()
201    }
202
203    /// Returns `true` if the [`Receipts`] vector is empty.
204    pub fn is_empty(&self) -> bool {
205        self.receipt_vec.is_empty()
206    }
207
208    /// Push a new vector of receipts into the [`Receipts`] collection.
209    pub fn push(&mut self, receipts: Vec<T>) {
210        self.receipt_vec.push(receipts);
211    }
212}
213
214impl<T> From<Vec<T>> for Receipts<T> {
215    fn from(block_receipts: Vec<T>) -> Self {
216        Self { receipt_vec: vec![block_receipts] }
217    }
218}
219
220impl<T> FromIterator<Vec<T>> for Receipts<T> {
221    fn from_iter<I: IntoIterator<Item = Vec<T>>>(iter: I) -> Self {
222        Self { receipt_vec: iter.into_iter().collect() }
223    }
224}
225
226impl<T: Encodable> Encodable for Receipts<T> {
227    fn encode(&self, out: &mut dyn BufMut) {
228        self.receipt_vec.encode(out)
229    }
230
231    fn length(&self) -> usize {
232        self.receipt_vec.length()
233    }
234}
235
236impl<T: Decodable> Decodable for Receipts<T> {
237    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
238        Ok(Self { receipt_vec: Decodable::decode(buf)? })
239    }
240}
241
242impl<T> Default for Receipts<T> {
243    fn default() -> Self {
244        Self { receipt_vec: Default::default() }
245    }
246}
247
248/// [`Receipt`] with calculated bloom filter.
249///
250/// This convenience type allows us to lazily calculate the bloom filter for a
251/// receipt, similar to [`Sealed`].
252///
253/// [`Sealed`]: crate::Sealed
254#[derive(Clone, Debug, Default, PartialEq, Eq)]
255#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
256#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
257#[doc(alias = "TransactionReceiptWithBloom", alias = "TxReceiptWithBloom")]
258pub struct ReceiptWithBloom<T = Receipt<Log>> {
259    #[cfg_attr(feature = "serde", serde(flatten))]
260    /// The receipt.
261    pub receipt: T,
262    /// The bloom filter.
263    pub logs_bloom: Bloom,
264}
265
266impl<R> TxReceipt for ReceiptWithBloom<R>
267where
268    R: TxReceipt,
269{
270    type Log = R::Log;
271
272    fn status_or_post_state(&self) -> Eip658Value {
273        self.receipt.status_or_post_state()
274    }
275
276    fn status(&self) -> bool {
277        self.receipt.status()
278    }
279
280    fn bloom(&self) -> Bloom {
281        self.logs_bloom
282    }
283
284    fn bloom_cheap(&self) -> Option<Bloom> {
285        Some(self.logs_bloom)
286    }
287
288    fn cumulative_gas_used(&self) -> u64 {
289        self.receipt.cumulative_gas_used()
290    }
291
292    fn logs(&self) -> &[Self::Log] {
293        self.receipt.logs()
294    }
295}
296
297impl<R> From<R> for ReceiptWithBloom<R>
298where
299    R: TxReceipt,
300{
301    fn from(receipt: R) -> Self {
302        let logs_bloom = receipt.bloom();
303        Self { logs_bloom, receipt }
304    }
305}
306
307impl<R> ReceiptWithBloom<R> {
308    /// Converts the receipt type by applying the given closure to it.
309    ///
310    /// Returns the type with the new receipt type.
311    pub fn map_receipt<U>(self, f: impl FnOnce(R) -> U) -> ReceiptWithBloom<U> {
312        let Self { receipt, logs_bloom } = self;
313        ReceiptWithBloom { receipt: f(receipt), logs_bloom }
314    }
315
316    /// Create new [ReceiptWithBloom]
317    pub const fn new(receipt: R, logs_bloom: Bloom) -> Self {
318        Self { receipt, logs_bloom }
319    }
320
321    /// Consume the structure, returning the receipt and the bloom filter
322    pub fn into_components(self) -> (R, Bloom) {
323        (self.receipt, self.logs_bloom)
324    }
325
326    /// Returns a reference to the bloom.
327    pub const fn bloom_ref(&self) -> &Bloom {
328        &self.logs_bloom
329    }
330}
331
332impl<L> ReceiptWithBloom<Receipt<L>> {
333    /// Converts the receipt's log type by applying a function to each log.
334    ///
335    /// Returns the receipt with the new log type.
336    pub fn map_logs<U>(self, f: impl FnMut(L) -> U) -> ReceiptWithBloom<Receipt<U>> {
337        let Self { receipt, logs_bloom } = self;
338        ReceiptWithBloom { receipt: receipt.map_logs(f), logs_bloom }
339    }
340
341    /// Converts a [`ReceiptWithBloom`] with a custom log type into a [`ReceiptWithBloom`] with the
342    /// primitives [`Log`] type by converting the logs.
343    ///
344    /// This is useful if log types that embed the primitives log type, e.g. the log receipt rpc
345    /// type.
346    pub fn into_primitives_receipt(self) -> ReceiptWithBloom<Receipt<Log>>
347    where
348        L: Into<Log>,
349    {
350        self.map_logs(Into::into)
351    }
352}
353
354impl<R: RlpEncodableReceipt> Encodable for ReceiptWithBloom<R> {
355    fn encode(&self, out: &mut dyn BufMut) {
356        self.receipt.rlp_encode_with_bloom(&self.logs_bloom, out);
357    }
358
359    fn length(&self) -> usize {
360        self.receipt.rlp_encoded_length_with_bloom(&self.logs_bloom)
361    }
362}
363
364impl<R: RlpDecodableReceipt> Decodable for ReceiptWithBloom<R> {
365    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
366        R::rlp_decode_with_bloom(buf)
367    }
368}
369
370impl<R: Typed2718> Typed2718 for ReceiptWithBloom<R> {
371    fn ty(&self) -> u8 {
372        self.receipt.ty()
373    }
374}
375
376impl<R> Encodable2718 for ReceiptWithBloom<R>
377where
378    R: Eip2718EncodableReceipt + Send + Sync,
379{
380    fn encode_2718_len(&self) -> usize {
381        self.receipt.eip2718_encoded_length_with_bloom(&self.logs_bloom)
382    }
383
384    fn encode_2718(&self, out: &mut dyn BufMut) {
385        self.receipt.eip2718_encode_with_bloom(&self.logs_bloom, out);
386    }
387}
388
389#[cfg(any(test, feature = "arbitrary"))]
390impl<'a, R> arbitrary::Arbitrary<'a> for ReceiptWithBloom<R>
391where
392    R: arbitrary::Arbitrary<'a>,
393{
394    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
395        Ok(Self { receipt: R::arbitrary(u)?, logs_bloom: Bloom::arbitrary(u)? })
396    }
397}
398
399#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
400pub(crate) mod serde_bincode_compat {
401    use alloc::borrow::Cow;
402    use serde::{Deserialize, Deserializer, Serialize, Serializer};
403    use serde_with::{DeserializeAs, SerializeAs};
404
405    /// Bincode-compatible [`super::Receipt`] serde implementation.
406    ///
407    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
408    /// ```rust
409    /// use alloy_consensus::{serde_bincode_compat, Receipt};
410    /// use serde::{de::DeserializeOwned, Deserialize, Serialize};
411    /// use serde_with::serde_as;
412    ///
413    /// #[serde_as]
414    /// #[derive(Serialize, Deserialize)]
415    /// struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
416    ///     #[serde_as(as = "serde_bincode_compat::Receipt<'_, T>")]
417    ///     receipt: Receipt<T>,
418    /// }
419    /// ```
420    #[derive(Debug, Serialize, Deserialize)]
421    pub struct Receipt<'a, T: Clone = alloy_primitives::Log> {
422        logs: Cow<'a, [T]>,
423        status: bool,
424        cumulative_gas_used: u64,
425    }
426
427    impl<'a, T: Clone> From<&'a super::Receipt<T>> for Receipt<'a, T> {
428        fn from(value: &'a super::Receipt<T>) -> Self {
429            Self {
430                logs: Cow::Borrowed(&value.logs),
431                // OP has no post state root variant
432                status: value.status.coerce_status(),
433                cumulative_gas_used: value.cumulative_gas_used,
434            }
435        }
436    }
437
438    impl<'a, T: Clone> From<Receipt<'a, T>> for super::Receipt<T> {
439        fn from(value: Receipt<'a, T>) -> Self {
440            Self {
441                status: value.status.into(),
442                cumulative_gas_used: value.cumulative_gas_used,
443                logs: value.logs.into_owned(),
444            }
445        }
446    }
447
448    impl<T: Serialize + Clone> SerializeAs<super::Receipt<T>> for Receipt<'_, T> {
449        fn serialize_as<S>(source: &super::Receipt<T>, serializer: S) -> Result<S::Ok, S::Error>
450        where
451            S: Serializer,
452        {
453            Receipt::<'_, T>::from(source).serialize(serializer)
454        }
455    }
456
457    impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::Receipt<T>> for Receipt<'de, T> {
458        fn deserialize_as<D>(deserializer: D) -> Result<super::Receipt<T>, D::Error>
459        where
460            D: Deserializer<'de>,
461        {
462            Receipt::<'_, T>::deserialize(deserializer).map(Into::into)
463        }
464    }
465
466    #[cfg(test)]
467    mod tests {
468        use super::super::{serde_bincode_compat, Receipt};
469        use alloy_primitives::Log;
470        use arbitrary::Arbitrary;
471        use bincode::config;
472        use rand::Rng;
473        use serde::{de::DeserializeOwned, Deserialize, Serialize};
474        use serde_with::serde_as;
475
476        #[test]
477        fn test_receipt_bincode_roundtrip() {
478            #[serde_as]
479            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
480            struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
481                #[serde_as(as = "serde_bincode_compat::Receipt<'_,T>")]
482                receipt: Receipt<T>,
483            }
484
485            let mut bytes = [0u8; 1024];
486            rand::thread_rng().fill(bytes.as_mut_slice());
487            let mut data = Data {
488                receipt: Receipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(),
489            };
490            // ensure we don't have an invalid poststate variant
491            data.receipt.status = data.receipt.status.coerce_status().into();
492
493            let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
494            let (decoded, _) =
495                bincode::serde::decode_from_slice::<Data<Log>, _>(&encoded, config::legacy())
496                    .unwrap();
497            assert_eq!(decoded, data);
498        }
499    }
500}
501
502#[cfg(test)]
503mod test {
504    use super::*;
505    use crate::ReceiptEnvelope;
506    use alloy_rlp::{Decodable, Encodable};
507
508    const fn assert_tx_receipt<T: TxReceipt>() {}
509
510    #[test]
511    const fn assert_receipt() {
512        assert_tx_receipt::<Receipt>();
513        assert_tx_receipt::<ReceiptWithBloom<Receipt>>();
514    }
515
516    #[cfg(feature = "serde")]
517    #[test]
518    fn root_vs_status() {
519        let receipt = super::Receipt::<()> {
520            status: super::Eip658Value::Eip658(true),
521            cumulative_gas_used: 0,
522            logs: Vec::new(),
523        };
524
525        let json = serde_json::to_string(&receipt).unwrap();
526        assert_eq!(json, r#"{"status":"0x1","cumulativeGasUsed":"0x0","logs":[]}"#);
527
528        let receipt = super::Receipt::<()> {
529            status: super::Eip658Value::PostState(Default::default()),
530            cumulative_gas_used: 0,
531            logs: Vec::new(),
532        };
533
534        let json = serde_json::to_string(&receipt).unwrap();
535        assert_eq!(
536            json,
537            r#"{"root":"0x0000000000000000000000000000000000000000000000000000000000000000","cumulativeGasUsed":"0x0","logs":[]}"#
538        );
539    }
540
541    #[cfg(feature = "serde")]
542    #[test]
543    fn deser_pre658() {
544        use alloy_primitives::b256;
545
546        let json = r#"{"root":"0x284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10","cumulativeGasUsed":"0x0","logs":[]}"#;
547
548        let receipt: super::Receipt<()> = serde_json::from_str(json).unwrap();
549
550        assert_eq!(
551            receipt.status,
552            super::Eip658Value::PostState(b256!(
553                "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
554            ))
555        );
556    }
557
558    #[test]
559    fn roundtrip_encodable_eip1559() {
560        let receipts =
561            Receipts { receipt_vec: vec![vec![ReceiptEnvelope::Eip1559(Default::default())]] };
562
563        let mut out = vec![];
564        receipts.encode(&mut out);
565
566        let mut out = out.as_slice();
567        let decoded = Receipts::<ReceiptEnvelope>::decode(&mut out).unwrap();
568
569        assert_eq!(receipts, decoded);
570    }
571}