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