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