alloy_consensus/receipt/
envelope.rs

1use core::fmt;
2
3use crate::{Eip658Value, Receipt, ReceiptWithBloom, TxReceipt, TxType};
4use alloy_eips::{
5    eip2718::{
6        Decodable2718, Eip2718Error, Eip2718Result, Encodable2718, EIP1559_TX_TYPE_ID,
7        EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
8    },
9    Typed2718,
10};
11use alloy_primitives::{Bloom, Log};
12use alloy_rlp::{BufMut, Decodable, Encodable};
13
14/// Receipt envelope, as defined in [EIP-2718].
15///
16/// This enum distinguishes between tagged and untagged legacy receipts, as the
17/// in-protocol Merkle tree may commit to EITHER 0-prefixed or raw. Therefore
18/// we must ensure that encoding returns the precise byte-array that was
19/// decoded, preserving the presence or absence of the `TransactionType` flag.
20///
21/// Transaction receipt payloads are specified in their respective EIPs.
22///
23/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718
24#[derive(Clone, Debug, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26#[cfg_attr(feature = "serde", serde(tag = "type"))]
27#[doc(alias = "TransactionReceiptEnvelope", alias = "TxReceiptEnvelope")]
28pub enum ReceiptEnvelope<T = Log> {
29    /// Receipt envelope with no type flag.
30    #[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))]
31    Legacy(ReceiptWithBloom<Receipt<T>>),
32    /// Receipt envelope with type flag 1, containing a [EIP-2930] receipt.
33    ///
34    /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930
35    #[cfg_attr(feature = "serde", serde(rename = "0x1", alias = "0x01"))]
36    Eip2930(ReceiptWithBloom<Receipt<T>>),
37    /// Receipt envelope with type flag 2, containing a [EIP-1559] receipt.
38    ///
39    /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559
40    #[cfg_attr(feature = "serde", serde(rename = "0x2", alias = "0x02"))]
41    Eip1559(ReceiptWithBloom<Receipt<T>>),
42    /// Receipt envelope with type flag 2, containing a [EIP-4844] receipt.
43    ///
44    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
45    #[cfg_attr(feature = "serde", serde(rename = "0x3", alias = "0x03"))]
46    Eip4844(ReceiptWithBloom<Receipt<T>>),
47    /// Receipt envelope with type flag 4, containing a [EIP-7702] receipt.
48    ///
49    /// [EIP-7702]: https://eips.ethereum.org/EIPS/eip-7702
50    #[cfg_attr(feature = "serde", serde(rename = "0x4", alias = "0x04"))]
51    Eip7702(ReceiptWithBloom<Receipt<T>>),
52}
53
54impl<T> ReceiptEnvelope<T> {
55    /// Converts the receipt's log type by applying a function to each log.
56    ///
57    /// Returns the receipt with the new log type.
58    pub fn map_logs<U>(self, f: impl FnMut(T) -> U) -> ReceiptEnvelope<U> {
59        match self {
60            Self::Legacy(r) => ReceiptEnvelope::Legacy(r.map_logs(f)),
61            Self::Eip2930(r) => ReceiptEnvelope::Eip2930(r.map_logs(f)),
62            Self::Eip1559(r) => ReceiptEnvelope::Eip1559(r.map_logs(f)),
63            Self::Eip4844(r) => ReceiptEnvelope::Eip4844(r.map_logs(f)),
64            Self::Eip7702(r) => ReceiptEnvelope::Eip7702(r.map_logs(f)),
65        }
66    }
67
68    /// Converts a [`ReceiptEnvelope`] with a custom log type into a [`ReceiptEnvelope`] with the
69    /// primitives [`Log`] type by converting the logs.
70    ///
71    /// This is useful if log types that embed the primitives log type, e.g. the log receipt rpc
72    /// type.
73    pub fn into_primitives_receipt(self) -> ReceiptEnvelope<Log>
74    where
75        T: Into<Log>,
76    {
77        self.map_logs(Into::into)
78    }
79
80    /// Return the [`TxType`] of the inner receipt.
81    #[doc(alias = "transaction_type")]
82    pub const fn tx_type(&self) -> TxType {
83        match self {
84            Self::Legacy(_) => TxType::Legacy,
85            Self::Eip2930(_) => TxType::Eip2930,
86            Self::Eip1559(_) => TxType::Eip1559,
87            Self::Eip4844(_) => TxType::Eip4844,
88            Self::Eip7702(_) => TxType::Eip7702,
89        }
90    }
91
92    /// Return true if the transaction was successful.
93    pub fn is_success(&self) -> bool {
94        self.status()
95    }
96
97    /// Returns the success status of the receipt's transaction.
98    pub fn status(&self) -> bool {
99        self.as_receipt().unwrap().status.coerce_status()
100    }
101
102    /// Returns the cumulative gas used at this receipt.
103    pub fn cumulative_gas_used(&self) -> u64 {
104        self.as_receipt().unwrap().cumulative_gas_used
105    }
106
107    /// Return the receipt logs.
108    pub fn logs(&self) -> &[T] {
109        &self.as_receipt().unwrap().logs
110    }
111
112    /// Return the receipt's bloom.
113    pub fn logs_bloom(&self) -> &Bloom {
114        &self.as_receipt_with_bloom().unwrap().logs_bloom
115    }
116
117    /// Return the inner receipt with bloom. Currently this is infallible,
118    /// however, future receipt types may be added.
119    pub const fn as_receipt_with_bloom(&self) -> Option<&ReceiptWithBloom<Receipt<T>>> {
120        match self {
121            Self::Legacy(t)
122            | Self::Eip2930(t)
123            | Self::Eip1559(t)
124            | Self::Eip4844(t)
125            | Self::Eip7702(t) => Some(t),
126        }
127    }
128
129    /// Return the mutable inner receipt with bloom. Currently this is
130    /// infallible, however, future receipt types may be added.
131    pub fn as_receipt_with_bloom_mut(&mut self) -> Option<&mut ReceiptWithBloom<Receipt<T>>> {
132        match self {
133            Self::Legacy(t)
134            | Self::Eip2930(t)
135            | Self::Eip1559(t)
136            | Self::Eip4844(t)
137            | Self::Eip7702(t) => Some(t),
138        }
139    }
140
141    /// Return the inner receipt. Currently this is infallible, however, future
142    /// receipt types may be added.
143    pub const fn as_receipt(&self) -> Option<&Receipt<T>> {
144        match self {
145            Self::Legacy(t)
146            | Self::Eip2930(t)
147            | Self::Eip1559(t)
148            | Self::Eip4844(t)
149            | Self::Eip7702(t) => Some(&t.receipt),
150        }
151    }
152}
153
154impl<T> TxReceipt for ReceiptEnvelope<T>
155where
156    T: Clone + fmt::Debug + PartialEq + Eq + Send + Sync,
157{
158    type Log = T;
159
160    fn status_or_post_state(&self) -> Eip658Value {
161        self.as_receipt().unwrap().status
162    }
163
164    fn status(&self) -> bool {
165        self.as_receipt().unwrap().status.coerce_status()
166    }
167
168    /// Return the receipt's bloom.
169    fn bloom(&self) -> Bloom {
170        self.as_receipt_with_bloom().unwrap().logs_bloom
171    }
172
173    fn bloom_cheap(&self) -> Option<Bloom> {
174        Some(self.bloom())
175    }
176
177    /// Returns the cumulative gas used at this receipt.
178    fn cumulative_gas_used(&self) -> u64 {
179        self.as_receipt().unwrap().cumulative_gas_used
180    }
181
182    /// Return the receipt logs.
183    fn logs(&self) -> &[T] {
184        &self.as_receipt().unwrap().logs
185    }
186}
187
188impl ReceiptEnvelope {
189    /// Get the length of the inner receipt in the 2718 encoding.
190    pub fn inner_length(&self) -> usize {
191        self.as_receipt_with_bloom().unwrap().length()
192    }
193
194    /// Calculate the length of the rlp payload of the network encoded receipt.
195    pub fn rlp_payload_length(&self) -> usize {
196        let length = self.as_receipt_with_bloom().unwrap().length();
197        match self {
198            Self::Legacy(_) => length,
199            _ => length + 1,
200        }
201    }
202}
203
204impl Encodable for ReceiptEnvelope {
205    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
206        self.network_encode(out)
207    }
208
209    fn length(&self) -> usize {
210        self.network_len()
211    }
212}
213
214impl Decodable for ReceiptEnvelope {
215    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
216        Self::network_decode(buf)
217            .map_or_else(|_| Err(alloy_rlp::Error::Custom("Unexpected type")), Ok)
218    }
219}
220
221impl Typed2718 for ReceiptEnvelope {
222    fn ty(&self) -> u8 {
223        match self {
224            Self::Legacy(_) => LEGACY_TX_TYPE_ID,
225            Self::Eip2930(_) => EIP2930_TX_TYPE_ID,
226            Self::Eip1559(_) => EIP1559_TX_TYPE_ID,
227            Self::Eip4844(_) => EIP4844_TX_TYPE_ID,
228            Self::Eip7702(_) => EIP7702_TX_TYPE_ID,
229        }
230    }
231}
232
233impl Encodable2718 for ReceiptEnvelope {
234    fn encode_2718_len(&self) -> usize {
235        self.inner_length() + !self.is_legacy() as usize
236    }
237
238    fn encode_2718(&self, out: &mut dyn BufMut) {
239        match self.type_flag() {
240            None => {}
241            Some(ty) => out.put_u8(ty),
242        }
243        self.as_receipt_with_bloom().unwrap().encode(out);
244    }
245}
246
247impl Decodable2718 for ReceiptEnvelope {
248    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
249        let receipt = Decodable::decode(buf)?;
250        match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("Unexpected type"))? {
251            TxType::Eip2930 => Ok(Self::Eip2930(receipt)),
252            TxType::Eip1559 => Ok(Self::Eip1559(receipt)),
253            TxType::Eip4844 => Ok(Self::Eip4844(receipt)),
254            TxType::Eip7702 => Ok(Self::Eip7702(receipt)),
255            TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
256        }
257    }
258
259    fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
260        Ok(Self::Legacy(Decodable::decode(buf)?))
261    }
262}
263
264#[cfg(any(test, feature = "arbitrary"))]
265impl<'a, T> arbitrary::Arbitrary<'a> for ReceiptEnvelope<T>
266where
267    T: arbitrary::Arbitrary<'a>,
268{
269    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
270        let receipt = ReceiptWithBloom::<Receipt<T>>::arbitrary(u)?;
271
272        match u.int_in_range(0..=3)? {
273            0 => Ok(Self::Legacy(receipt)),
274            1 => Ok(Self::Eip2930(receipt)),
275            2 => Ok(Self::Eip1559(receipt)),
276            3 => Ok(Self::Eip4844(receipt)),
277            4 => Ok(Self::Eip7702(receipt)),
278            _ => unreachable!(),
279        }
280    }
281}
282
283#[cfg(test)]
284mod test {
285    #[cfg(feature = "serde")]
286    #[test]
287    fn deser_pre658_receipt_envelope() {
288        use alloy_primitives::b256;
289
290        use crate::Receipt;
291
292        let receipt = super::ReceiptWithBloom::<Receipt<()>> {
293            receipt: super::Receipt {
294                status: super::Eip658Value::PostState(b256!(
295                    "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
296                )),
297                cumulative_gas_used: 0,
298                logs: Default::default(),
299            },
300            logs_bloom: Default::default(),
301        };
302
303        let json = serde_json::to_string(&receipt).unwrap();
304
305        println!("Serialized {}", json);
306
307        let receipt: super::ReceiptWithBloom<Receipt<()>> = serde_json::from_str(&json).unwrap();
308
309        assert_eq!(
310            receipt.receipt.status,
311            super::Eip658Value::PostState(b256!(
312                "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
313            ))
314        );
315    }
316}