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#[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 fn is_success(&self) -> bool {
94 self.status()
95 }
96
97 pub fn status(&self) -> bool {
99 self.as_receipt().unwrap().status.coerce_status()
100 }
101
102 pub 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 logs_bloom(&self) -> &Bloom {
114 &self.as_receipt_with_bloom().unwrap().logs_bloom
115 }
116
117 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 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 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 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 fn cumulative_gas_used(&self) -> u64 {
179 self.as_receipt().unwrap().cumulative_gas_used
180 }
181
182 fn logs(&self) -> &[T] {
184 &self.as_receipt().unwrap().logs
185 }
186}
187
188impl ReceiptEnvelope {
189 pub fn inner_length(&self) -> usize {
191 self.as_receipt_with_bloom().unwrap().length()
192 }
193
194 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(all(feature = "serde", feature = "serde-bincode-compat"))]
285pub(crate) mod serde_bincode_compat {
286 use crate::{Receipt, ReceiptWithBloom, TxType};
287 use alloc::borrow::Cow;
288 use alloy_primitives::{Bloom, Log, U8};
289 use serde::{Deserialize, Deserializer, Serialize, Serializer};
290 use serde_with::{DeserializeAs, SerializeAs};
291
292 #[derive(Debug, Serialize, Deserialize)]
308 pub struct ReceiptEnvelope<'a, T: Clone = Log> {
309 #[serde(deserialize_with = "deserde_txtype")]
310 tx_type: TxType,
311 success: bool,
312 cumulative_gas_used: u64,
313 logs_bloom: Cow<'a, Bloom>,
314 logs: Cow<'a, [T]>,
315 }
316
317 fn deserde_txtype<'de, D>(deserializer: D) -> Result<TxType, D::Error>
319 where
320 D: Deserializer<'de>,
321 {
322 let value = U8::deserialize(deserializer)?;
323 value.to::<u8>().try_into().map_err(serde::de::Error::custom)
324 }
325
326 impl<'a, T: Clone> From<&'a super::ReceiptEnvelope<T>> for ReceiptEnvelope<'a, T> {
327 fn from(value: &'a super::ReceiptEnvelope<T>) -> Self {
328 Self {
329 tx_type: value.tx_type(),
330 success: value.status(),
331 cumulative_gas_used: value.cumulative_gas_used(),
332 logs_bloom: Cow::Borrowed(value.logs_bloom()),
333 logs: Cow::Borrowed(value.logs()),
334 }
335 }
336 }
337
338 impl<'a, T: Clone> From<ReceiptEnvelope<'a, T>> for super::ReceiptEnvelope<T> {
339 fn from(value: ReceiptEnvelope<'a, T>) -> Self {
340 let ReceiptEnvelope { tx_type, success, cumulative_gas_used, logs_bloom, logs } = value;
341 let receipt = ReceiptWithBloom {
342 receipt: Receipt {
343 status: success.into(),
344 cumulative_gas_used,
345 logs: logs.into_owned(),
346 },
347 logs_bloom: logs_bloom.into_owned(),
348 };
349 match tx_type {
350 TxType::Legacy => Self::Legacy(receipt),
351 TxType::Eip2930 => Self::Eip2930(receipt),
352 TxType::Eip1559 => Self::Eip1559(receipt),
353 TxType::Eip4844 => Self::Eip4844(receipt),
354 TxType::Eip7702 => Self::Eip7702(receipt),
355 }
356 }
357 }
358
359 impl<T: Serialize + Clone> SerializeAs<super::ReceiptEnvelope<T>> for ReceiptEnvelope<'_, T> {
360 fn serialize_as<S>(
361 source: &super::ReceiptEnvelope<T>,
362 serializer: S,
363 ) -> Result<S::Ok, S::Error>
364 where
365 S: Serializer,
366 {
367 ReceiptEnvelope::<'_, T>::from(source).serialize(serializer)
368 }
369 }
370
371 impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::ReceiptEnvelope<T>>
372 for ReceiptEnvelope<'de, T>
373 {
374 fn deserialize_as<D>(deserializer: D) -> Result<super::ReceiptEnvelope<T>, D::Error>
375 where
376 D: Deserializer<'de>,
377 {
378 ReceiptEnvelope::<'_, T>::deserialize(deserializer).map(Into::into)
379 }
380 }
381
382 #[cfg(test)]
383 mod tests {
384 use super::super::{serde_bincode_compat, ReceiptEnvelope};
385 use alloy_primitives::Log;
386 use arbitrary::Arbitrary;
387 use rand::Rng;
388 use serde::{Deserialize, Serialize};
389 use serde_with::serde_as;
390
391 #[test]
392 fn test_receipt_evelope_bincode_roundtrip() {
393 #[serde_as]
394 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
395 struct Data {
396 #[serde_as(as = "serde_bincode_compat::ReceiptEnvelope<'_>")]
397 transaction: ReceiptEnvelope<Log>,
398 }
399
400 let mut bytes = [0u8; 1024];
401 rand::thread_rng().fill(bytes.as_mut_slice());
402 let mut data = Data {
403 transaction: ReceiptEnvelope::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
404 .unwrap(),
405 };
406
407 data.transaction.as_receipt_with_bloom_mut().unwrap().receipt.status = true.into();
409
410 let encoded = bincode::serialize(&data).unwrap();
411 let decoded: Data = bincode::deserialize(&encoded).unwrap();
412 assert_eq!(decoded, data);
413 }
414 }
415}
416
417#[cfg(test)]
418mod test {
419 #[cfg(feature = "serde")]
420 #[test]
421 fn deser_pre658_receipt_envelope() {
422 use alloy_primitives::b256;
423
424 use crate::Receipt;
425
426 let receipt = super::ReceiptWithBloom::<Receipt<()>> {
427 receipt: super::Receipt {
428 status: super::Eip658Value::PostState(b256!(
429 "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
430 )),
431 cumulative_gas_used: 0,
432 logs: Default::default(),
433 },
434 logs_bloom: Default::default(),
435 };
436
437 let json = serde_json::to_string(&receipt).unwrap();
438
439 println!("Serialized {}", json);
440
441 let receipt: super::ReceiptWithBloom<Receipt<()>> = serde_json::from_str(&json).unwrap();
442
443 assert_eq!(
444 receipt.receipt.status,
445 super::Eip658Value::PostState(b256!(
446 "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
447 ))
448 );
449 }
450}