1use crate::{Eip658Value, Receipt, ReceiptWithBloom, TxReceipt, TxType};
2use alloc::vec::Vec;
3use alloy_eips::{
4    eip2718::{
5        Decodable2718, Eip2718Error, Eip2718Result, Encodable2718, IsTyped2718, EIP1559_TX_TYPE_ID,
6        EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
7    },
8    Typed2718,
9};
10use alloy_primitives::{Bloom, Log};
11use alloy_rlp::{BufMut, Decodable, Encodable};
12use core::fmt;
13
14#[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    #[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))]
31    Legacy(ReceiptWithBloom<Receipt<T>>),
32    #[cfg_attr(feature = "serde", serde(rename = "0x1", alias = "0x01"))]
36    Eip2930(ReceiptWithBloom<Receipt<T>>),
37    #[cfg_attr(feature = "serde", serde(rename = "0x2", alias = "0x02"))]
41    Eip1559(ReceiptWithBloom<Receipt<T>>),
42    #[cfg_attr(feature = "serde", serde(rename = "0x3", alias = "0x03"))]
46    Eip4844(ReceiptWithBloom<Receipt<T>>),
47    #[cfg_attr(feature = "serde", serde(rename = "0x4", alias = "0x04"))]
51    Eip7702(ReceiptWithBloom<Receipt<T>>),
52}
53
54impl<T> ReceiptEnvelope<T> {
55    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    pub fn into_primitives_receipt(self) -> ReceiptEnvelope<Log>
74    where
75        T: Into<Log>,
76    {
77        self.map_logs(Into::into)
78    }
79
80    #[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    pub const fn is_success(&self) -> bool {
94        self.status()
95    }
96
97    pub const fn status(&self) -> bool {
99        self.as_receipt().unwrap().status.coerce_status()
100    }
101
102    pub const fn cumulative_gas_used(&self) -> u64 {
104        self.as_receipt().unwrap().cumulative_gas_used
105    }
106
107    pub fn logs(&self) -> &[T] {
109        &self.as_receipt().unwrap().logs
110    }
111
112    pub fn into_logs(self) -> Vec<T> {
114        self.into_receipt().logs
115    }
116
117    pub const fn logs_bloom(&self) -> &Bloom {
119        &self.as_receipt_with_bloom().unwrap().logs_bloom
120    }
121
122    pub const fn as_receipt_with_bloom(&self) -> Option<&ReceiptWithBloom<Receipt<T>>> {
125        match self {
126            Self::Legacy(t)
127            | Self::Eip2930(t)
128            | Self::Eip1559(t)
129            | Self::Eip4844(t)
130            | Self::Eip7702(t) => Some(t),
131        }
132    }
133
134    pub const fn as_receipt_with_bloom_mut(&mut self) -> Option<&mut ReceiptWithBloom<Receipt<T>>> {
137        match self {
138            Self::Legacy(t)
139            | Self::Eip2930(t)
140            | Self::Eip1559(t)
141            | Self::Eip4844(t)
142            | Self::Eip7702(t) => Some(t),
143        }
144    }
145
146    pub fn into_receipt(self) -> Receipt<T> {
148        match self {
149            Self::Legacy(t)
150            | Self::Eip2930(t)
151            | Self::Eip1559(t)
152            | Self::Eip4844(t)
153            | Self::Eip7702(t) => t.receipt,
154        }
155    }
156
157    pub const fn as_receipt(&self) -> Option<&Receipt<T>> {
160        match self {
161            Self::Legacy(t)
162            | Self::Eip2930(t)
163            | Self::Eip1559(t)
164            | Self::Eip4844(t)
165            | Self::Eip7702(t) => Some(&t.receipt),
166        }
167    }
168}
169
170impl<T> TxReceipt for ReceiptEnvelope<T>
171where
172    T: Clone + fmt::Debug + PartialEq + Eq + Send + Sync,
173{
174    type Log = T;
175
176    fn status_or_post_state(&self) -> Eip658Value {
177        self.as_receipt().unwrap().status
178    }
179
180    fn status(&self) -> bool {
181        self.as_receipt().unwrap().status.coerce_status()
182    }
183
184    fn bloom(&self) -> Bloom {
186        self.as_receipt_with_bloom().unwrap().logs_bloom
187    }
188
189    fn bloom_cheap(&self) -> Option<Bloom> {
190        Some(self.bloom())
191    }
192
193    fn cumulative_gas_used(&self) -> u64 {
195        self.as_receipt().unwrap().cumulative_gas_used
196    }
197
198    fn logs(&self) -> &[T] {
200        &self.as_receipt().unwrap().logs
201    }
202
203    fn into_logs(self) -> Vec<Self::Log>
204    where
205        Self::Log: Clone,
206    {
207        self.into_receipt().logs
208    }
209}
210
211impl ReceiptEnvelope {
212    pub fn inner_length(&self) -> usize {
214        self.as_receipt_with_bloom().unwrap().length()
215    }
216
217    pub fn rlp_payload_length(&self) -> usize {
219        let length = self.as_receipt_with_bloom().unwrap().length();
220        match self {
221            Self::Legacy(_) => length,
222            _ => length + 1,
223        }
224    }
225}
226
227impl Encodable for ReceiptEnvelope {
228    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
229        self.network_encode(out)
230    }
231
232    fn length(&self) -> usize {
233        self.network_len()
234    }
235}
236
237impl Decodable for ReceiptEnvelope {
238    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
239        Self::network_decode(buf)
240            .map_or_else(|_| Err(alloy_rlp::Error::Custom("Unexpected type")), Ok)
241    }
242}
243
244impl Typed2718 for ReceiptEnvelope {
245    fn ty(&self) -> u8 {
246        match self {
247            Self::Legacy(_) => LEGACY_TX_TYPE_ID,
248            Self::Eip2930(_) => EIP2930_TX_TYPE_ID,
249            Self::Eip1559(_) => EIP1559_TX_TYPE_ID,
250            Self::Eip4844(_) => EIP4844_TX_TYPE_ID,
251            Self::Eip7702(_) => EIP7702_TX_TYPE_ID,
252        }
253    }
254}
255
256impl IsTyped2718 for ReceiptEnvelope {
257    fn is_type(type_id: u8) -> bool {
258        <TxType as IsTyped2718>::is_type(type_id)
259    }
260}
261
262impl Encodable2718 for ReceiptEnvelope {
263    fn encode_2718_len(&self) -> usize {
264        self.inner_length() + !self.is_legacy() as usize
265    }
266
267    fn encode_2718(&self, out: &mut dyn BufMut) {
268        match self.type_flag() {
269            None => {}
270            Some(ty) => out.put_u8(ty),
271        }
272        self.as_receipt_with_bloom().unwrap().encode(out);
273    }
274}
275
276impl Decodable2718 for ReceiptEnvelope {
277    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
278        let receipt = Decodable::decode(buf)?;
279        match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("Unexpected type"))? {
280            TxType::Eip2930 => Ok(Self::Eip2930(receipt)),
281            TxType::Eip1559 => Ok(Self::Eip1559(receipt)),
282            TxType::Eip4844 => Ok(Self::Eip4844(receipt)),
283            TxType::Eip7702 => Ok(Self::Eip7702(receipt)),
284            TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
285        }
286    }
287
288    fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
289        Ok(Self::Legacy(Decodable::decode(buf)?))
290    }
291}
292
293#[cfg(any(test, feature = "arbitrary"))]
294impl<'a, T> arbitrary::Arbitrary<'a> for ReceiptEnvelope<T>
295where
296    T: arbitrary::Arbitrary<'a>,
297{
298    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
299        let receipt = ReceiptWithBloom::<Receipt<T>>::arbitrary(u)?;
300
301        match u.int_in_range(0..=3)? {
302            0 => Ok(Self::Legacy(receipt)),
303            1 => Ok(Self::Eip2930(receipt)),
304            2 => Ok(Self::Eip1559(receipt)),
305            3 => Ok(Self::Eip4844(receipt)),
306            4 => Ok(Self::Eip7702(receipt)),
307            _ => unreachable!(),
308        }
309    }
310}
311
312#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
314pub(crate) mod serde_bincode_compat {
315    use crate::{Receipt, ReceiptWithBloom, TxType};
316    use alloc::borrow::Cow;
317    use alloy_primitives::{Bloom, Log, U8};
318    use serde::{Deserialize, Deserializer, Serialize, Serializer};
319    use serde_with::{DeserializeAs, SerializeAs};
320
321    #[derive(Debug, Serialize, Deserialize)]
337    pub struct ReceiptEnvelope<'a, T: Clone = Log> {
338        #[serde(deserialize_with = "deserde_txtype")]
339        tx_type: TxType,
340        success: bool,
341        cumulative_gas_used: u64,
342        logs_bloom: Cow<'a, Bloom>,
343        logs: Cow<'a, [T]>,
344    }
345
346    fn deserde_txtype<'de, D>(deserializer: D) -> Result<TxType, D::Error>
348    where
349        D: Deserializer<'de>,
350    {
351        let value = U8::deserialize(deserializer)?;
352        value.to::<u8>().try_into().map_err(serde::de::Error::custom)
353    }
354
355    impl<'a, T: Clone> From<&'a super::ReceiptEnvelope<T>> for ReceiptEnvelope<'a, T> {
356        fn from(value: &'a super::ReceiptEnvelope<T>) -> Self {
357            Self {
358                tx_type: value.tx_type(),
359                success: value.status(),
360                cumulative_gas_used: value.cumulative_gas_used(),
361                logs_bloom: Cow::Borrowed(value.logs_bloom()),
362                logs: Cow::Borrowed(value.logs()),
363            }
364        }
365    }
366
367    impl<'a, T: Clone> From<ReceiptEnvelope<'a, T>> for super::ReceiptEnvelope<T> {
368        fn from(value: ReceiptEnvelope<'a, T>) -> Self {
369            let ReceiptEnvelope { tx_type, success, cumulative_gas_used, logs_bloom, logs } = value;
370            let receipt = ReceiptWithBloom {
371                receipt: Receipt {
372                    status: success.into(),
373                    cumulative_gas_used,
374                    logs: logs.into_owned(),
375                },
376                logs_bloom: logs_bloom.into_owned(),
377            };
378            match tx_type {
379                TxType::Legacy => Self::Legacy(receipt),
380                TxType::Eip2930 => Self::Eip2930(receipt),
381                TxType::Eip1559 => Self::Eip1559(receipt),
382                TxType::Eip4844 => Self::Eip4844(receipt),
383                TxType::Eip7702 => Self::Eip7702(receipt),
384            }
385        }
386    }
387
388    impl<T: Serialize + Clone> SerializeAs<super::ReceiptEnvelope<T>> for ReceiptEnvelope<'_, T> {
389        fn serialize_as<S>(
390            source: &super::ReceiptEnvelope<T>,
391            serializer: S,
392        ) -> Result<S::Ok, S::Error>
393        where
394            S: Serializer,
395        {
396            ReceiptEnvelope::<'_, T>::from(source).serialize(serializer)
397        }
398    }
399
400    impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::ReceiptEnvelope<T>>
401        for ReceiptEnvelope<'de, T>
402    {
403        fn deserialize_as<D>(deserializer: D) -> Result<super::ReceiptEnvelope<T>, D::Error>
404        where
405            D: Deserializer<'de>,
406        {
407            ReceiptEnvelope::<'_, T>::deserialize(deserializer).map(Into::into)
408        }
409    }
410
411    #[cfg(test)]
412    mod tests {
413        use super::super::{serde_bincode_compat, ReceiptEnvelope};
414        use alloy_primitives::Log;
415        use arbitrary::Arbitrary;
416        use bincode::config;
417        use rand::Rng;
418        use serde::{Deserialize, Serialize};
419        use serde_with::serde_as;
420
421        #[test]
422        fn test_receipt_envelope_bincode_roundtrip() {
423            #[serde_as]
424            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
425            struct Data {
426                #[serde_as(as = "serde_bincode_compat::ReceiptEnvelope<'_>")]
427                transaction: ReceiptEnvelope<Log>,
428            }
429
430            let mut bytes = [0u8; 1024];
431            rand::thread_rng().fill(bytes.as_mut_slice());
432            let mut data = Data {
433                transaction: ReceiptEnvelope::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
434                    .unwrap(),
435            };
436
437            data.transaction.as_receipt_with_bloom_mut().unwrap().receipt.status = true.into();
439
440            let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
441            let (decoded, _) =
442                bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
443            assert_eq!(decoded, data);
444        }
445    }
446}
447
448#[cfg(test)]
449mod test {
450    #[cfg(feature = "serde")]
451    #[test]
452    fn deser_pre658_receipt_envelope() {
453        use alloy_primitives::b256;
454
455        use crate::Receipt;
456
457        let receipt = super::ReceiptWithBloom::<Receipt<()>> {
458            receipt: super::Receipt {
459                status: super::Eip658Value::PostState(b256!(
460                    "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
461                )),
462                cumulative_gas_used: 0,
463                logs: Default::default(),
464            },
465            logs_bloom: Default::default(),
466        };
467
468        let json = serde_json::to_string(&receipt).unwrap();
469
470        println!("Serialized {json}");
471
472        let receipt: super::ReceiptWithBloom<Receipt<()>> = serde_json::from_str(&json).unwrap();
473
474        assert_eq!(
475            receipt.receipt.status,
476            super::Eip658Value::PostState(b256!(
477                "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
478            ))
479        );
480    }
481}