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))]
26#[cfg_attr(feature = "serde", serde(tag = "type"))]
27#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
28#[doc(alias = "TransactionReceiptEnvelope", alias = "TxReceiptEnvelope")]
29pub enum ReceiptEnvelope<T = Log> {
30 #[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))]
32 Legacy(ReceiptWithBloom<Receipt<T>>),
33 #[cfg_attr(feature = "serde", serde(rename = "0x1", alias = "0x01"))]
37 Eip2930(ReceiptWithBloom<Receipt<T>>),
38 #[cfg_attr(feature = "serde", serde(rename = "0x2", alias = "0x02"))]
42 Eip1559(ReceiptWithBloom<Receipt<T>>),
43 #[cfg_attr(feature = "serde", serde(rename = "0x3", alias = "0x03"))]
47 Eip4844(ReceiptWithBloom<Receipt<T>>),
48 #[cfg_attr(feature = "serde", serde(rename = "0x4", alias = "0x04"))]
52 Eip7702(ReceiptWithBloom<Receipt<T>>),
53}
54
55#[cfg(feature = "serde")]
64impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for ReceiptEnvelope<T> {
65 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
66 #[derive(serde::Deserialize)]
67 struct ReceiptEnvelopeHelper<T> {
68 #[serde(default, rename = "type", with = "alloy_serde::quantity::opt")]
69 ty: Option<u8>,
70 #[serde(flatten)]
71 receipt: ReceiptWithBloom<Receipt<T>>,
72 }
73
74 let helper = ReceiptEnvelopeHelper::<T>::deserialize(deserializer)?;
75 let ty = TxType::try_from(helper.ty.unwrap_or(LEGACY_TX_TYPE_ID))
76 .map_err(serde::de::Error::custom)?;
77 Ok(Self::from_typed(ty, helper.receipt))
78 }
79}
80
81impl<T> ReceiptEnvelope<T> {
82 pub fn from_typed<R>(tx_type: TxType, receipt: R) -> Self
84 where
85 R: Into<ReceiptWithBloom<Receipt<T>>>,
86 {
87 match tx_type {
88 TxType::Legacy => Self::Legacy(receipt.into()),
89 TxType::Eip2930 => Self::Eip2930(receipt.into()),
90 TxType::Eip1559 => Self::Eip1559(receipt.into()),
91 TxType::Eip4844 => Self::Eip4844(receipt.into()),
92 TxType::Eip7702 => Self::Eip7702(receipt.into()),
93 }
94 }
95
96 pub fn map_logs<U>(self, f: impl FnMut(T) -> U) -> ReceiptEnvelope<U> {
100 match self {
101 Self::Legacy(r) => ReceiptEnvelope::Legacy(r.map_logs(f)),
102 Self::Eip2930(r) => ReceiptEnvelope::Eip2930(r.map_logs(f)),
103 Self::Eip1559(r) => ReceiptEnvelope::Eip1559(r.map_logs(f)),
104 Self::Eip4844(r) => ReceiptEnvelope::Eip4844(r.map_logs(f)),
105 Self::Eip7702(r) => ReceiptEnvelope::Eip7702(r.map_logs(f)),
106 }
107 }
108
109 pub fn into_primitives_receipt(self) -> ReceiptEnvelope<Log>
115 where
116 T: Into<Log>,
117 {
118 self.map_logs(Into::into)
119 }
120
121 #[doc(alias = "transaction_type")]
123 pub const fn tx_type(&self) -> TxType {
124 match self {
125 Self::Legacy(_) => TxType::Legacy,
126 Self::Eip2930(_) => TxType::Eip2930,
127 Self::Eip1559(_) => TxType::Eip1559,
128 Self::Eip4844(_) => TxType::Eip4844,
129 Self::Eip7702(_) => TxType::Eip7702,
130 }
131 }
132
133 pub const fn is_success(&self) -> bool {
135 self.status()
136 }
137
138 pub const fn status(&self) -> bool {
140 self.as_receipt().unwrap().status.coerce_status()
141 }
142
143 pub const fn cumulative_gas_used(&self) -> u64 {
145 self.as_receipt().unwrap().cumulative_gas_used
146 }
147
148 pub fn logs(&self) -> &[T] {
150 &self.as_receipt().unwrap().logs
151 }
152
153 pub fn into_logs(self) -> Vec<T> {
155 self.into_receipt().logs
156 }
157
158 pub const fn logs_bloom(&self) -> &Bloom {
160 &self.as_receipt_with_bloom().unwrap().logs_bloom
161 }
162
163 pub const fn as_receipt_with_bloom(&self) -> Option<&ReceiptWithBloom<Receipt<T>>> {
166 match self {
167 Self::Legacy(t)
168 | Self::Eip2930(t)
169 | Self::Eip1559(t)
170 | Self::Eip4844(t)
171 | Self::Eip7702(t) => Some(t),
172 }
173 }
174
175 pub const fn as_receipt_with_bloom_mut(&mut self) -> Option<&mut ReceiptWithBloom<Receipt<T>>> {
178 match self {
179 Self::Legacy(t)
180 | Self::Eip2930(t)
181 | Self::Eip1559(t)
182 | Self::Eip4844(t)
183 | Self::Eip7702(t) => Some(t),
184 }
185 }
186
187 pub fn into_receipt(self) -> Receipt<T> {
189 match self {
190 Self::Legacy(t)
191 | Self::Eip2930(t)
192 | Self::Eip1559(t)
193 | Self::Eip4844(t)
194 | Self::Eip7702(t) => t.receipt,
195 }
196 }
197
198 pub const fn as_receipt(&self) -> Option<&Receipt<T>> {
201 match self {
202 Self::Legacy(t)
203 | Self::Eip2930(t)
204 | Self::Eip1559(t)
205 | Self::Eip4844(t)
206 | Self::Eip7702(t) => Some(&t.receipt),
207 }
208 }
209}
210
211impl<T> TxReceipt for ReceiptEnvelope<T>
212where
213 T: Clone + fmt::Debug + PartialEq + Eq + Send + Sync,
214{
215 type Log = T;
216
217 fn status_or_post_state(&self) -> Eip658Value {
218 self.as_receipt().unwrap().status
219 }
220
221 fn status(&self) -> bool {
222 self.as_receipt().unwrap().status.coerce_status()
223 }
224
225 fn bloom(&self) -> Bloom {
227 self.as_receipt_with_bloom().unwrap().logs_bloom
228 }
229
230 fn bloom_cheap(&self) -> Option<Bloom> {
231 Some(self.bloom())
232 }
233
234 fn cumulative_gas_used(&self) -> u64 {
236 self.as_receipt().unwrap().cumulative_gas_used
237 }
238
239 fn logs(&self) -> &[T] {
241 &self.as_receipt().unwrap().logs
242 }
243
244 fn into_logs(self) -> Vec<Self::Log>
245 where
246 Self::Log: Clone,
247 {
248 self.into_receipt().logs
249 }
250}
251
252impl ReceiptEnvelope {
253 pub fn inner_length(&self) -> usize {
255 self.as_receipt_with_bloom().unwrap().length()
256 }
257
258 pub fn rlp_payload_length(&self) -> usize {
260 let length = self.as_receipt_with_bloom().unwrap().length();
261 match self {
262 Self::Legacy(_) => length,
263 _ => length + 1,
264 }
265 }
266}
267
268impl Encodable for ReceiptEnvelope {
269 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
270 self.network_encode(out)
271 }
272
273 fn length(&self) -> usize {
274 self.network_len()
275 }
276}
277
278impl Decodable for ReceiptEnvelope {
279 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
280 Self::network_decode(buf)
281 .map_or_else(|_| Err(alloy_rlp::Error::Custom("Unexpected type")), Ok)
282 }
283}
284
285impl Typed2718 for ReceiptEnvelope {
286 fn ty(&self) -> u8 {
287 match self {
288 Self::Legacy(_) => LEGACY_TX_TYPE_ID,
289 Self::Eip2930(_) => EIP2930_TX_TYPE_ID,
290 Self::Eip1559(_) => EIP1559_TX_TYPE_ID,
291 Self::Eip4844(_) => EIP4844_TX_TYPE_ID,
292 Self::Eip7702(_) => EIP7702_TX_TYPE_ID,
293 }
294 }
295}
296
297impl IsTyped2718 for ReceiptEnvelope {
298 fn is_type(type_id: u8) -> bool {
299 <TxType as IsTyped2718>::is_type(type_id)
300 }
301}
302
303impl Encodable2718 for ReceiptEnvelope {
304 fn encode_2718_len(&self) -> usize {
305 self.inner_length() + !self.is_legacy() as usize
306 }
307
308 fn encode_2718(&self, out: &mut dyn BufMut) {
309 match self.type_flag() {
310 None => {}
311 Some(ty) => out.put_u8(ty),
312 }
313 self.as_receipt_with_bloom().unwrap().encode(out);
314 }
315}
316
317impl Decodable2718 for ReceiptEnvelope {
318 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
319 let receipt = Decodable::decode(buf)?;
320 match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("Unexpected type"))? {
321 TxType::Eip2930 => Ok(Self::Eip2930(receipt)),
322 TxType::Eip1559 => Ok(Self::Eip1559(receipt)),
323 TxType::Eip4844 => Ok(Self::Eip4844(receipt)),
324 TxType::Eip7702 => Ok(Self::Eip7702(receipt)),
325 TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
326 }
327 }
328
329 fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
330 Ok(Self::Legacy(Decodable::decode(buf)?))
331 }
332}
333
334#[cfg(any(test, feature = "arbitrary"))]
335impl<'a, T> arbitrary::Arbitrary<'a> for ReceiptEnvelope<T>
336where
337 T: arbitrary::Arbitrary<'a>,
338{
339 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
340 let receipt = ReceiptWithBloom::<Receipt<T>>::arbitrary(u)?;
341
342 match u.int_in_range(0..=4)? {
343 0 => Ok(Self::Legacy(receipt)),
344 1 => Ok(Self::Eip2930(receipt)),
345 2 => Ok(Self::Eip1559(receipt)),
346 3 => Ok(Self::Eip4844(receipt)),
347 4 => Ok(Self::Eip7702(receipt)),
348 _ => unreachable!(),
349 }
350 }
351}
352
353#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
355pub(crate) mod serde_bincode_compat {
356 use crate::{Receipt, ReceiptWithBloom, TxType};
357 use alloc::borrow::Cow;
358 use alloy_primitives::{Bloom, Log, U8};
359 use serde::{Deserialize, Deserializer, Serialize, Serializer};
360 use serde_with::{DeserializeAs, SerializeAs};
361
362 #[derive(Debug, Serialize, Deserialize)]
378 pub struct ReceiptEnvelope<'a, T: Clone = Log> {
379 #[serde(deserialize_with = "deserde_txtype")]
380 tx_type: TxType,
381 success: bool,
382 cumulative_gas_used: u64,
383 logs_bloom: Cow<'a, Bloom>,
384 logs: Cow<'a, [T]>,
385 }
386
387 fn deserde_txtype<'de, D>(deserializer: D) -> Result<TxType, D::Error>
389 where
390 D: Deserializer<'de>,
391 {
392 let value = U8::deserialize(deserializer)?;
393 value.to::<u8>().try_into().map_err(serde::de::Error::custom)
394 }
395
396 impl<'a, T: Clone> From<&'a super::ReceiptEnvelope<T>> for ReceiptEnvelope<'a, T> {
397 fn from(value: &'a super::ReceiptEnvelope<T>) -> Self {
398 Self {
399 tx_type: value.tx_type(),
400 success: value.status(),
401 cumulative_gas_used: value.cumulative_gas_used(),
402 logs_bloom: Cow::Borrowed(value.logs_bloom()),
403 logs: Cow::Borrowed(value.logs()),
404 }
405 }
406 }
407
408 impl<'a, T: Clone> From<ReceiptEnvelope<'a, T>> for super::ReceiptEnvelope<T> {
409 fn from(value: ReceiptEnvelope<'a, T>) -> Self {
410 let ReceiptEnvelope { tx_type, success, cumulative_gas_used, logs_bloom, logs } = value;
411 let receipt = ReceiptWithBloom {
412 receipt: Receipt {
413 status: success.into(),
414 cumulative_gas_used,
415 logs: logs.into_owned(),
416 },
417 logs_bloom: logs_bloom.into_owned(),
418 };
419 match tx_type {
420 TxType::Legacy => Self::Legacy(receipt),
421 TxType::Eip2930 => Self::Eip2930(receipt),
422 TxType::Eip1559 => Self::Eip1559(receipt),
423 TxType::Eip4844 => Self::Eip4844(receipt),
424 TxType::Eip7702 => Self::Eip7702(receipt),
425 }
426 }
427 }
428
429 impl<T: Serialize + Clone> SerializeAs<super::ReceiptEnvelope<T>> for ReceiptEnvelope<'_, T> {
430 fn serialize_as<S>(
431 source: &super::ReceiptEnvelope<T>,
432 serializer: S,
433 ) -> Result<S::Ok, S::Error>
434 where
435 S: Serializer,
436 {
437 ReceiptEnvelope::<'_, T>::from(source).serialize(serializer)
438 }
439 }
440
441 impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::ReceiptEnvelope<T>>
442 for ReceiptEnvelope<'de, T>
443 {
444 fn deserialize_as<D>(deserializer: D) -> Result<super::ReceiptEnvelope<T>, D::Error>
445 where
446 D: Deserializer<'de>,
447 {
448 ReceiptEnvelope::<'_, T>::deserialize(deserializer).map(Into::into)
449 }
450 }
451
452 #[cfg(test)]
453 mod tests {
454 use super::super::{serde_bincode_compat, ReceiptEnvelope};
455 use alloy_primitives::Log;
456 use arbitrary::Arbitrary;
457 use bincode::config;
458 use rand::Rng;
459 use serde::{Deserialize, Serialize};
460 use serde_with::serde_as;
461
462 #[test]
463 fn test_receipt_envelope_bincode_roundtrip() {
464 #[serde_as]
465 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
466 struct Data {
467 #[serde_as(as = "serde_bincode_compat::ReceiptEnvelope<'_>")]
468 transaction: ReceiptEnvelope<Log>,
469 }
470
471 let mut bytes = [0u8; 1024];
472 rand::thread_rng().fill(bytes.as_mut_slice());
473 let mut data = Data {
474 transaction: ReceiptEnvelope::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
475 .unwrap(),
476 };
477
478 data.transaction.as_receipt_with_bloom_mut().unwrap().receipt.status = true.into();
480
481 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
482 let (decoded, _) =
483 bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
484 assert_eq!(decoded, data);
485 }
486 }
487}
488
489#[cfg(test)]
490mod test {
491 use crate::{Receipt, ReceiptEnvelope, TxType};
492 use alloy_primitives::Log;
493
494 #[cfg(feature = "serde")]
495 #[test]
496 fn deser_pre658_receipt_envelope() {
497 use crate::Receipt;
498 use alloy_primitives::b256;
499
500 let receipt = super::ReceiptWithBloom::<Receipt<()>> {
501 receipt: super::Receipt {
502 status: super::Eip658Value::PostState(b256!(
503 "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
504 )),
505 cumulative_gas_used: 0,
506 logs: Default::default(),
507 },
508 logs_bloom: Default::default(),
509 };
510
511 let json = serde_json::to_string(&receipt).unwrap();
512
513 println!("Serialized {json}");
514
515 let receipt: super::ReceiptWithBloom<Receipt<()>> = serde_json::from_str(&json).unwrap();
516
517 assert_eq!(
518 receipt.receipt.status,
519 super::Eip658Value::PostState(b256!(
520 "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
521 ))
522 );
523 }
524
525 #[cfg(feature = "serde")]
526 #[test]
527 fn deser_receipt_envelope_without_type() {
528 let inner = super::ReceiptWithBloom::<Receipt<()>> {
529 receipt: Receipt {
530 status: super::Eip658Value::Eip658(true),
531 cumulative_gas_used: 0xc3b68,
532 logs: Default::default(),
533 },
534 logs_bloom: Default::default(),
535 };
536 let mut json = serde_json::to_value(&inner).unwrap();
537 assert!(json.get("type").is_none());
538
539 let envelope: ReceiptEnvelope<()> = serde_json::from_value(json.clone()).unwrap();
540 assert_eq!(envelope, ReceiptEnvelope::Legacy(inner.clone()));
541
542 json["type"] = "0x2".into();
544 let envelope: ReceiptEnvelope<()> = serde_json::from_value(json.clone()).unwrap();
545 assert_eq!(envelope, ReceiptEnvelope::Eip1559(inner));
546
547 json["type"] = "0x7f".into();
549 serde_json::from_value::<ReceiptEnvelope<()>>(json).unwrap_err();
550 }
551
552 #[test]
553 fn convert_envelope() {
554 let receipt = Receipt::<Log>::default();
555 let _envelope = ReceiptEnvelope::from_typed(TxType::Eip7702, receipt);
556 }
557}