reth_ethereum_primitives/
receipt.rs

1use alloc::vec::Vec;
2use alloy_consensus::{
3    Eip2718EncodableReceipt, Eip658Value, ReceiptWithBloom, RlpDecodableReceipt,
4    RlpEncodableReceipt, TxReceipt, TxType, Typed2718,
5};
6use alloy_primitives::{Bloom, Log};
7use alloy_rlp::{BufMut, Decodable, Encodable, Header};
8use reth_primitives_traits::InMemorySize;
9use serde::{Deserialize, Serialize};
10
11/// Typed ethereum transaction receipt.
12/// Receipt containing result of transaction execution.
13#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
14#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
15#[cfg_attr(feature = "reth-codec", derive(reth_codecs::CompactZstd))]
16#[cfg_attr(feature = "reth-codec", reth_codecs::add_arbitrary_tests)]
17#[cfg_attr(feature = "reth-codec", reth_zstd(
18    compressor = reth_zstd_compressors::RECEIPT_COMPRESSOR,
19    decompressor = reth_zstd_compressors::RECEIPT_DECOMPRESSOR
20))]
21pub struct Receipt {
22    /// Receipt type.
23    #[serde(with = "tx_type_serde")]
24    pub tx_type: TxType,
25    /// If transaction is executed successfully.
26    ///
27    /// This is the `statusCode`
28    pub success: bool,
29    /// Gas used
30    pub cumulative_gas_used: u64,
31    /// Log send from contracts.
32    pub logs: Vec<Log>,
33}
34
35impl Receipt {
36    /// Returns length of RLP-encoded receipt fields with the given [`Bloom`] without an RLP header.
37    pub fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize {
38        self.success.length() +
39            self.cumulative_gas_used.length() +
40            bloom.length() +
41            self.logs.length()
42    }
43
44    /// RLP-encodes receipt fields with the given [`Bloom`] without an RLP header.
45    pub fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn BufMut) {
46        self.success.encode(out);
47        self.cumulative_gas_used.encode(out);
48        bloom.encode(out);
49        self.logs.encode(out);
50    }
51
52    /// Returns RLP header for inner encoding.
53    pub fn rlp_header_inner(&self, bloom: &Bloom) -> Header {
54        Header { list: true, payload_length: self.rlp_encoded_fields_length(bloom) }
55    }
56
57    /// RLP-decodes the receipt from the provided buffer. This does not expect a type byte or
58    /// network header.
59    pub fn rlp_decode_inner(
60        buf: &mut &[u8],
61        tx_type: TxType,
62    ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
63        let header = Header::decode(buf)?;
64        if !header.list {
65            return Err(alloy_rlp::Error::UnexpectedString);
66        }
67
68        let remaining = buf.len();
69
70        let success = Decodable::decode(buf)?;
71        let cumulative_gas_used = Decodable::decode(buf)?;
72        let logs_bloom = Decodable::decode(buf)?;
73        let logs = Decodable::decode(buf)?;
74
75        if buf.len() + header.payload_length != remaining {
76            return Err(alloy_rlp::Error::UnexpectedLength);
77        }
78
79        Ok(ReceiptWithBloom {
80            receipt: Self { cumulative_gas_used, tx_type, success, logs },
81            logs_bloom,
82        })
83    }
84}
85
86impl Eip2718EncodableReceipt for Receipt {
87    fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
88        !self.tx_type.is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload()
89    }
90
91    fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
92        if !self.tx_type.is_legacy() {
93            out.put_u8(self.tx_type as u8);
94        }
95        self.rlp_header_inner(bloom).encode(out);
96        self.rlp_encode_fields(bloom, out);
97    }
98}
99
100impl RlpEncodableReceipt for Receipt {
101    fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
102        let mut len = self.eip2718_encoded_length_with_bloom(bloom);
103        if !self.tx_type.is_legacy() {
104            len += Header {
105                list: false,
106                payload_length: self.eip2718_encoded_length_with_bloom(bloom),
107            }
108            .length();
109        }
110
111        len
112    }
113
114    fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
115        if !self.tx_type.is_legacy() {
116            Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom) }
117                .encode(out);
118        }
119        self.eip2718_encode_with_bloom(bloom, out);
120    }
121}
122
123impl RlpDecodableReceipt for Receipt {
124    fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
125        let header_buf = &mut &**buf;
126        let header = Header::decode(header_buf)?;
127
128        // Legacy receipt, reuse initial buffer without advancing
129        if header.list {
130            return Self::rlp_decode_inner(buf, TxType::Legacy)
131        }
132
133        // Otherwise, advance the buffer and try decoding type flag followed by receipt
134        *buf = *header_buf;
135
136        let remaining = buf.len();
137        let tx_type = TxType::decode(buf)?;
138        let this = Self::rlp_decode_inner(buf, tx_type)?;
139
140        if buf.len() + header.payload_length != remaining {
141            return Err(alloy_rlp::Error::UnexpectedLength);
142        }
143
144        Ok(this)
145    }
146}
147
148impl TxReceipt for Receipt {
149    type Log = Log;
150
151    fn status_or_post_state(&self) -> Eip658Value {
152        self.success.into()
153    }
154
155    fn status(&self) -> bool {
156        self.success
157    }
158
159    fn bloom(&self) -> Bloom {
160        alloy_primitives::logs_bloom(self.logs())
161    }
162
163    fn cumulative_gas_used(&self) -> u128 {
164        self.cumulative_gas_used as u128
165    }
166
167    fn logs(&self) -> &[Log] {
168        &self.logs
169    }
170}
171
172impl Typed2718 for Receipt {
173    fn ty(&self) -> u8 {
174        self.tx_type as u8
175    }
176}
177
178impl InMemorySize for Receipt {
179    fn size(&self) -> usize {
180        self.tx_type.size() +
181            core::mem::size_of::<bool>() +
182            core::mem::size_of::<u64>() +
183            self.logs.capacity() * core::mem::size_of::<Log>()
184    }
185}
186
187impl reth_primitives_traits::Receipt for Receipt {}
188
189/// TODO: Remove once <https://github.com/alloy-rs/alloy/pull/1780> is released.
190mod tx_type_serde {
191    use alloy_primitives::{U64, U8};
192
193    use super::*;
194
195    pub(crate) fn serialize<S>(tx_type: &TxType, serializer: S) -> Result<S::Ok, S::Error>
196    where
197        S: serde::Serializer,
198    {
199        let value: U8 = (*tx_type).into();
200        value.serialize(serializer)
201    }
202
203    pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<TxType, D::Error>
204    where
205        D: serde::Deserializer<'de>,
206    {
207        U64::deserialize(deserializer)?.try_into().map_err(serde::de::Error::custom)
208    }
209}