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#[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
55impl<T> ReceiptEnvelope<T> {
56 pub fn from_typed<R>(tx_type: TxType, receipt: R) -> Self
58 where
59 R: Into<ReceiptWithBloom<Receipt<T>>>,
60 {
61 match tx_type {
62 TxType::Legacy => Self::Legacy(receipt.into()),
63 TxType::Eip2930 => Self::Eip2930(receipt.into()),
64 TxType::Eip1559 => Self::Eip1559(receipt.into()),
65 TxType::Eip4844 => Self::Eip4844(receipt.into()),
66 TxType::Eip7702 => Self::Eip7702(receipt.into()),
67 }
68 }
69
70 pub fn map_logs<U>(self, f: impl FnMut(T) -> U) -> ReceiptEnvelope<U> {
74 match self {
75 Self::Legacy(r) => ReceiptEnvelope::Legacy(r.map_logs(f)),
76 Self::Eip2930(r) => ReceiptEnvelope::Eip2930(r.map_logs(f)),
77 Self::Eip1559(r) => ReceiptEnvelope::Eip1559(r.map_logs(f)),
78 Self::Eip4844(r) => ReceiptEnvelope::Eip4844(r.map_logs(f)),
79 Self::Eip7702(r) => ReceiptEnvelope::Eip7702(r.map_logs(f)),
80 }
81 }
82
83 pub fn into_primitives_receipt(self) -> ReceiptEnvelope<Log>
89 where
90 T: Into<Log>,
91 {
92 self.map_logs(Into::into)
93 }
94
95 #[doc(alias = "transaction_type")]
97 pub const fn tx_type(&self) -> TxType {
98 match self {
99 Self::Legacy(_) => TxType::Legacy,
100 Self::Eip2930(_) => TxType::Eip2930,
101 Self::Eip1559(_) => TxType::Eip1559,
102 Self::Eip4844(_) => TxType::Eip4844,
103 Self::Eip7702(_) => TxType::Eip7702,
104 }
105 }
106
107 pub const fn is_success(&self) -> bool {
109 self.status()
110 }
111
112 pub const fn status(&self) -> bool {
114 self.as_receipt().unwrap().status.coerce_status()
115 }
116
117 pub const fn cumulative_gas_used(&self) -> u64 {
119 self.as_receipt().unwrap().cumulative_gas_used
120 }
121
122 pub fn logs(&self) -> &[T] {
124 &self.as_receipt().unwrap().logs
125 }
126
127 pub fn into_logs(self) -> Vec<T> {
129 self.into_receipt().logs
130 }
131
132 pub const fn logs_bloom(&self) -> &Bloom {
134 &self.as_receipt_with_bloom().unwrap().logs_bloom
135 }
136
137 pub const fn as_receipt_with_bloom(&self) -> Option<&ReceiptWithBloom<Receipt<T>>> {
140 match self {
141 Self::Legacy(t)
142 | Self::Eip2930(t)
143 | Self::Eip1559(t)
144 | Self::Eip4844(t)
145 | Self::Eip7702(t) => Some(t),
146 }
147 }
148
149 pub const fn as_receipt_with_bloom_mut(&mut self) -> Option<&mut ReceiptWithBloom<Receipt<T>>> {
152 match self {
153 Self::Legacy(t)
154 | Self::Eip2930(t)
155 | Self::Eip1559(t)
156 | Self::Eip4844(t)
157 | Self::Eip7702(t) => Some(t),
158 }
159 }
160
161 pub fn into_receipt(self) -> Receipt<T> {
163 match self {
164 Self::Legacy(t)
165 | Self::Eip2930(t)
166 | Self::Eip1559(t)
167 | Self::Eip4844(t)
168 | Self::Eip7702(t) => t.receipt,
169 }
170 }
171
172 pub const fn as_receipt(&self) -> Option<&Receipt<T>> {
175 match self {
176 Self::Legacy(t)
177 | Self::Eip2930(t)
178 | Self::Eip1559(t)
179 | Self::Eip4844(t)
180 | Self::Eip7702(t) => Some(&t.receipt),
181 }
182 }
183}
184
185impl<T> TxReceipt for ReceiptEnvelope<T>
186where
187 T: Clone + fmt::Debug + PartialEq + Eq + Send + Sync,
188{
189 type Log = T;
190
191 fn status_or_post_state(&self) -> Eip658Value {
192 self.as_receipt().unwrap().status
193 }
194
195 fn status(&self) -> bool {
196 self.as_receipt().unwrap().status.coerce_status()
197 }
198
199 fn bloom(&self) -> Bloom {
201 self.as_receipt_with_bloom().unwrap().logs_bloom
202 }
203
204 fn bloom_cheap(&self) -> Option<Bloom> {
205 Some(self.bloom())
206 }
207
208 fn cumulative_gas_used(&self) -> u64 {
210 self.as_receipt().unwrap().cumulative_gas_used
211 }
212
213 fn logs(&self) -> &[T] {
215 &self.as_receipt().unwrap().logs
216 }
217
218 fn into_logs(self) -> Vec<Self::Log>
219 where
220 Self::Log: Clone,
221 {
222 self.into_receipt().logs
223 }
224}
225
226impl ReceiptEnvelope {
227 pub fn inner_length(&self) -> usize {
229 self.as_receipt_with_bloom().unwrap().length()
230 }
231
232 pub fn rlp_payload_length(&self) -> usize {
234 let length = self.as_receipt_with_bloom().unwrap().length();
235 match self {
236 Self::Legacy(_) => length,
237 _ => length + 1,
238 }
239 }
240}
241
242impl Encodable for ReceiptEnvelope {
243 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
244 self.network_encode(out)
245 }
246
247 fn length(&self) -> usize {
248 self.network_len()
249 }
250}
251
252impl Decodable for ReceiptEnvelope {
253 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
254 Self::network_decode(buf)
255 .map_or_else(|_| Err(alloy_rlp::Error::Custom("Unexpected type")), Ok)
256 }
257}
258
259impl Typed2718 for ReceiptEnvelope {
260 fn ty(&self) -> u8 {
261 match self {
262 Self::Legacy(_) => LEGACY_TX_TYPE_ID,
263 Self::Eip2930(_) => EIP2930_TX_TYPE_ID,
264 Self::Eip1559(_) => EIP1559_TX_TYPE_ID,
265 Self::Eip4844(_) => EIP4844_TX_TYPE_ID,
266 Self::Eip7702(_) => EIP7702_TX_TYPE_ID,
267 }
268 }
269}
270
271impl IsTyped2718 for ReceiptEnvelope {
272 fn is_type(type_id: u8) -> bool {
273 <TxType as IsTyped2718>::is_type(type_id)
274 }
275}
276
277impl Encodable2718 for ReceiptEnvelope {
278 fn encode_2718_len(&self) -> usize {
279 self.inner_length() + !self.is_legacy() as usize
280 }
281
282 fn encode_2718(&self, out: &mut dyn BufMut) {
283 match self.type_flag() {
284 None => {}
285 Some(ty) => out.put_u8(ty),
286 }
287 self.as_receipt_with_bloom().unwrap().encode(out);
288 }
289}
290
291impl Decodable2718 for ReceiptEnvelope {
292 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
293 let receipt = Decodable::decode(buf)?;
294 match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("Unexpected type"))? {
295 TxType::Eip2930 => Ok(Self::Eip2930(receipt)),
296 TxType::Eip1559 => Ok(Self::Eip1559(receipt)),
297 TxType::Eip4844 => Ok(Self::Eip4844(receipt)),
298 TxType::Eip7702 => Ok(Self::Eip7702(receipt)),
299 TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
300 }
301 }
302
303 fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
304 Ok(Self::Legacy(Decodable::decode(buf)?))
305 }
306}
307
308#[cfg(any(test, feature = "arbitrary"))]
309impl<'a, T> arbitrary::Arbitrary<'a> for ReceiptEnvelope<T>
310where
311 T: arbitrary::Arbitrary<'a>,
312{
313 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
314 let receipt = ReceiptWithBloom::<Receipt<T>>::arbitrary(u)?;
315
316 match u.int_in_range(0..=4)? {
317 0 => Ok(Self::Legacy(receipt)),
318 1 => Ok(Self::Eip2930(receipt)),
319 2 => Ok(Self::Eip1559(receipt)),
320 3 => Ok(Self::Eip4844(receipt)),
321 4 => Ok(Self::Eip7702(receipt)),
322 _ => unreachable!(),
323 }
324 }
325}
326
327#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
329pub(crate) mod serde_bincode_compat {
330 use crate::{Receipt, ReceiptWithBloom, TxType};
331 use alloc::borrow::Cow;
332 use alloy_primitives::{Bloom, Log, U8};
333 use serde::{Deserialize, Deserializer, Serialize, Serializer};
334 use serde_with::{DeserializeAs, SerializeAs};
335
336 #[derive(Debug, Serialize, Deserialize)]
352 pub struct ReceiptEnvelope<'a, T: Clone = Log> {
353 #[serde(deserialize_with = "deserde_txtype")]
354 tx_type: TxType,
355 success: bool,
356 cumulative_gas_used: u64,
357 logs_bloom: Cow<'a, Bloom>,
358 logs: Cow<'a, [T]>,
359 }
360
361 fn deserde_txtype<'de, D>(deserializer: D) -> Result<TxType, D::Error>
363 where
364 D: Deserializer<'de>,
365 {
366 let value = U8::deserialize(deserializer)?;
367 value.to::<u8>().try_into().map_err(serde::de::Error::custom)
368 }
369
370 impl<'a, T: Clone> From<&'a super::ReceiptEnvelope<T>> for ReceiptEnvelope<'a, T> {
371 fn from(value: &'a super::ReceiptEnvelope<T>) -> Self {
372 Self {
373 tx_type: value.tx_type(),
374 success: value.status(),
375 cumulative_gas_used: value.cumulative_gas_used(),
376 logs_bloom: Cow::Borrowed(value.logs_bloom()),
377 logs: Cow::Borrowed(value.logs()),
378 }
379 }
380 }
381
382 impl<'a, T: Clone> From<ReceiptEnvelope<'a, T>> for super::ReceiptEnvelope<T> {
383 fn from(value: ReceiptEnvelope<'a, T>) -> Self {
384 let ReceiptEnvelope { tx_type, success, cumulative_gas_used, logs_bloom, logs } = value;
385 let receipt = ReceiptWithBloom {
386 receipt: Receipt {
387 status: success.into(),
388 cumulative_gas_used,
389 logs: logs.into_owned(),
390 },
391 logs_bloom: logs_bloom.into_owned(),
392 };
393 match tx_type {
394 TxType::Legacy => Self::Legacy(receipt),
395 TxType::Eip2930 => Self::Eip2930(receipt),
396 TxType::Eip1559 => Self::Eip1559(receipt),
397 TxType::Eip4844 => Self::Eip4844(receipt),
398 TxType::Eip7702 => Self::Eip7702(receipt),
399 }
400 }
401 }
402
403 impl<T: Serialize + Clone> SerializeAs<super::ReceiptEnvelope<T>> for ReceiptEnvelope<'_, T> {
404 fn serialize_as<S>(
405 source: &super::ReceiptEnvelope<T>,
406 serializer: S,
407 ) -> Result<S::Ok, S::Error>
408 where
409 S: Serializer,
410 {
411 ReceiptEnvelope::<'_, T>::from(source).serialize(serializer)
412 }
413 }
414
415 impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::ReceiptEnvelope<T>>
416 for ReceiptEnvelope<'de, T>
417 {
418 fn deserialize_as<D>(deserializer: D) -> Result<super::ReceiptEnvelope<T>, D::Error>
419 where
420 D: Deserializer<'de>,
421 {
422 ReceiptEnvelope::<'_, T>::deserialize(deserializer).map(Into::into)
423 }
424 }
425
426 #[cfg(test)]
427 mod tests {
428 use super::super::{serde_bincode_compat, ReceiptEnvelope};
429 use alloy_primitives::Log;
430 use arbitrary::Arbitrary;
431 use bincode::config;
432 use rand::Rng;
433 use serde::{Deserialize, Serialize};
434 use serde_with::serde_as;
435
436 #[test]
437 fn test_receipt_envelope_bincode_roundtrip() {
438 #[serde_as]
439 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
440 struct Data {
441 #[serde_as(as = "serde_bincode_compat::ReceiptEnvelope<'_>")]
442 transaction: ReceiptEnvelope<Log>,
443 }
444
445 let mut bytes = [0u8; 1024];
446 rand::thread_rng().fill(bytes.as_mut_slice());
447 let mut data = Data {
448 transaction: ReceiptEnvelope::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
449 .unwrap(),
450 };
451
452 data.transaction.as_receipt_with_bloom_mut().unwrap().receipt.status = true.into();
454
455 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
456 let (decoded, _) =
457 bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
458 assert_eq!(decoded, data);
459 }
460 }
461}
462
463#[cfg(test)]
464mod test {
465 use crate::{Receipt, ReceiptEnvelope, TxType};
466 use alloy_primitives::Log;
467
468 #[cfg(feature = "serde")]
469 #[test]
470 fn deser_pre658_receipt_envelope() {
471 use crate::Receipt;
472 use alloy_primitives::b256;
473
474 let receipt = super::ReceiptWithBloom::<Receipt<()>> {
475 receipt: super::Receipt {
476 status: super::Eip658Value::PostState(b256!(
477 "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
478 )),
479 cumulative_gas_used: 0,
480 logs: Default::default(),
481 },
482 logs_bloom: Default::default(),
483 };
484
485 let json = serde_json::to_string(&receipt).unwrap();
486
487 println!("Serialized {json}");
488
489 let receipt: super::ReceiptWithBloom<Receipt<()>> = serde_json::from_str(&json).unwrap();
490
491 assert_eq!(
492 receipt.receipt.status,
493 super::Eip658Value::PostState(b256!(
494 "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
495 ))
496 );
497 }
498
499 #[test]
500 fn convert_envelope() {
501 let receipt = Receipt::<Log>::default();
502 let _envelope = ReceiptEnvelope::from_typed(TxType::Eip7702, receipt);
503 }
504}