foundry_primitives/transaction/
receipt.rs1use alloy_consensus::{
2 Eip658Value, Receipt, ReceiptEnvelope, ReceiptWithBloom, TxReceipt, Typed2718,
3};
4use alloy_network::eip2718::{
5 Decodable2718, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
6 Eip2718Error, Encodable2718, LEGACY_TX_TYPE_ID,
7};
8use alloy_primitives::{Bloom, Log, logs_bloom};
9use alloy_rlp::{BufMut, Decodable, Encodable, Header, bytes};
10use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, OpDepositReceipt, OpDepositReceiptWithBloom};
11use serde::{Deserialize, Serialize};
12
13use crate::FoundryTxType;
14
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(tag = "type")]
17pub enum FoundryReceiptEnvelope<T = Log> {
18 #[serde(rename = "0x0", alias = "0x00")]
19 Legacy(ReceiptWithBloom<Receipt<T>>),
20 #[serde(rename = "0x1", alias = "0x01")]
21 Eip2930(ReceiptWithBloom<Receipt<T>>),
22 #[serde(rename = "0x2", alias = "0x02")]
23 Eip1559(ReceiptWithBloom<Receipt<T>>),
24 #[serde(rename = "0x3", alias = "0x03")]
25 Eip4844(ReceiptWithBloom<Receipt<T>>),
26 #[serde(rename = "0x4", alias = "0x04")]
27 Eip7702(ReceiptWithBloom<Receipt<T>>),
28 #[serde(rename = "0x7E", alias = "0x7e")]
29 Deposit(OpDepositReceiptWithBloom<T>),
30}
31
32impl FoundryReceiptEnvelope<Log> {
33 pub fn from_parts<'a>(
35 status: bool,
36 cumulative_gas_used: u64,
37 logs: impl IntoIterator<Item = &'a Log>,
38 tx_type: FoundryTxType,
39 deposit_nonce: Option<u64>,
40 deposit_receipt_version: Option<u64>,
41 ) -> Self {
42 let logs = logs.into_iter().cloned().collect::<Vec<_>>();
43 let logs_bloom = logs_bloom(&logs);
44 let inner_receipt =
45 Receipt { status: Eip658Value::Eip658(status), cumulative_gas_used, logs };
46 match tx_type {
47 FoundryTxType::Legacy => {
48 Self::Legacy(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
49 }
50 FoundryTxType::Eip2930 => {
51 Self::Eip2930(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
52 }
53 FoundryTxType::Eip1559 => {
54 Self::Eip1559(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
55 }
56 FoundryTxType::Eip4844 => {
57 Self::Eip4844(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
58 }
59 FoundryTxType::Eip7702 => {
60 Self::Eip7702(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
61 }
62 FoundryTxType::Deposit => {
63 let inner = OpDepositReceiptWithBloom {
64 receipt: OpDepositReceipt {
65 inner: inner_receipt,
66 deposit_nonce,
67 deposit_receipt_version,
68 },
69 logs_bloom,
70 };
71 Self::Deposit(inner)
72 }
73 }
74 }
75}
76
77impl<T> FoundryReceiptEnvelope<T> {
78 pub const fn tx_type(&self) -> FoundryTxType {
80 match self {
81 Self::Legacy(_) => FoundryTxType::Legacy,
82 Self::Eip2930(_) => FoundryTxType::Eip2930,
83 Self::Eip1559(_) => FoundryTxType::Eip1559,
84 Self::Eip4844(_) => FoundryTxType::Eip4844,
85 Self::Eip7702(_) => FoundryTxType::Eip7702,
86 Self::Deposit(_) => FoundryTxType::Deposit,
87 }
88 }
89
90 pub const fn is_success(&self) -> bool {
92 self.status()
93 }
94
95 pub const fn status(&self) -> bool {
97 self.as_receipt().status.coerce_status()
98 }
99
100 pub const fn cumulative_gas_used(&self) -> u64 {
102 self.as_receipt().cumulative_gas_used
103 }
104
105 pub fn map_logs<U>(self, f: impl FnMut(T) -> U) -> FoundryReceiptEnvelope<U> {
109 match self {
110 Self::Legacy(r) => FoundryReceiptEnvelope::Legacy(r.map_logs(f)),
111 Self::Eip2930(r) => FoundryReceiptEnvelope::Eip2930(r.map_logs(f)),
112 Self::Eip1559(r) => FoundryReceiptEnvelope::Eip1559(r.map_logs(f)),
113 Self::Eip4844(r) => FoundryReceiptEnvelope::Eip4844(r.map_logs(f)),
114 Self::Eip7702(r) => FoundryReceiptEnvelope::Eip7702(r.map_logs(f)),
115 Self::Deposit(r) => FoundryReceiptEnvelope::Deposit(r.map_receipt(|r| r.map_logs(f))),
116 }
117 }
118
119 pub fn logs(&self) -> &[T] {
121 &self.as_receipt().logs
122 }
123
124 pub fn into_logs(self) -> Vec<T> {
126 self.into_receipt().logs
127 }
128
129 pub const fn logs_bloom(&self) -> &Bloom {
131 match self {
132 Self::Legacy(t) => &t.logs_bloom,
133 Self::Eip2930(t) => &t.logs_bloom,
134 Self::Eip1559(t) => &t.logs_bloom,
135 Self::Eip4844(t) => &t.logs_bloom,
136 Self::Eip7702(t) => &t.logs_bloom,
137 Self::Deposit(t) => &t.logs_bloom,
138 }
139 }
140
141 pub fn deposit_nonce(&self) -> Option<u64> {
143 self.as_deposit_receipt().and_then(|r| r.deposit_nonce)
144 }
145
146 pub fn deposit_receipt_version(&self) -> Option<u64> {
148 self.as_deposit_receipt().and_then(|r| r.deposit_receipt_version)
149 }
150
151 pub const fn as_deposit_receipt_with_bloom(&self) -> Option<&OpDepositReceiptWithBloom<T>> {
153 match self {
154 Self::Deposit(t) => Some(t),
155 _ => None,
156 }
157 }
158
159 pub const fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt<T>> {
161 match self {
162 Self::Deposit(t) => Some(&t.receipt),
163 _ => None,
164 }
165 }
166
167 pub fn into_receipt(self) -> Receipt<T> {
169 match self {
170 Self::Legacy(t)
171 | Self::Eip2930(t)
172 | Self::Eip1559(t)
173 | Self::Eip4844(t)
174 | Self::Eip7702(t) => t.receipt,
175 Self::Deposit(t) => t.receipt.into_inner(),
176 }
177 }
178
179 pub const fn as_receipt(&self) -> &Receipt<T> {
181 match self {
182 Self::Legacy(t)
183 | Self::Eip2930(t)
184 | Self::Eip1559(t)
185 | Self::Eip4844(t)
186 | Self::Eip7702(t) => &t.receipt,
187 Self::Deposit(t) => &t.receipt.inner,
188 }
189 }
190}
191
192impl FoundryReceiptEnvelope {
193 pub fn inner_length(&self) -> usize {
195 match self {
196 Self::Legacy(t) => t.length(),
197 Self::Eip2930(t) => t.length(),
198 Self::Eip1559(t) => t.length(),
199 Self::Eip4844(t) => t.length(),
200 Self::Eip7702(t) => t.length(),
201 Self::Deposit(t) => t.length(),
202 }
203 }
204
205 pub fn rlp_payload_length(&self) -> usize {
207 let length = self.inner_length();
208 match self {
209 Self::Legacy(_) => length,
210 _ => length + 1,
211 }
212 }
213}
214
215impl<T> TxReceipt for FoundryReceiptEnvelope<T>
216where
217 T: Clone + core::fmt::Debug + PartialEq + Eq + Send + Sync,
218{
219 type Log = T;
220
221 fn status_or_post_state(&self) -> Eip658Value {
222 self.as_receipt().status
223 }
224
225 fn status(&self) -> bool {
226 self.as_receipt().status.coerce_status()
227 }
228
229 fn bloom(&self) -> Bloom {
231 *self.logs_bloom()
232 }
233
234 fn bloom_cheap(&self) -> Option<Bloom> {
235 Some(self.bloom())
236 }
237
238 fn cumulative_gas_used(&self) -> u64 {
240 self.as_receipt().cumulative_gas_used
241 }
242
243 fn logs(&self) -> &[T] {
245 &self.as_receipt().logs
246 }
247}
248
249impl Encodable for FoundryReceiptEnvelope {
250 fn encode(&self, out: &mut dyn bytes::BufMut) {
251 match self {
252 Self::Legacy(r) => r.encode(out),
253 receipt => {
254 let payload_len = match receipt {
255 Self::Eip2930(r) => r.length() + 1,
256 Self::Eip1559(r) => r.length() + 1,
257 Self::Eip4844(r) => r.length() + 1,
258 Self::Eip7702(r) => r.length() + 1,
259 Self::Deposit(r) => r.length() + 1,
260 _ => unreachable!("receipt already matched"),
261 };
262
263 match receipt {
264 Self::Eip2930(r) => {
265 Header { list: true, payload_length: payload_len }.encode(out);
266 EIP2930_TX_TYPE_ID.encode(out);
267 r.encode(out);
268 }
269 Self::Eip1559(r) => {
270 Header { list: true, payload_length: payload_len }.encode(out);
271 EIP1559_TX_TYPE_ID.encode(out);
272 r.encode(out);
273 }
274 Self::Eip4844(r) => {
275 Header { list: true, payload_length: payload_len }.encode(out);
276 EIP4844_TX_TYPE_ID.encode(out);
277 r.encode(out);
278 }
279 Self::Eip7702(r) => {
280 Header { list: true, payload_length: payload_len }.encode(out);
281 EIP7702_TX_TYPE_ID.encode(out);
282 r.encode(out);
283 }
284 Self::Deposit(r) => {
285 Header { list: true, payload_length: payload_len }.encode(out);
286 DEPOSIT_TX_TYPE_ID.encode(out);
287 r.encode(out);
288 }
289 _ => unreachable!("receipt already matched"),
290 }
291 }
292 }
293 }
294}
295
296impl Decodable for FoundryReceiptEnvelope {
297 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
298 use bytes::Buf;
299 use std::cmp::Ordering;
300
301 let rlp_type = *buf
305 .first()
306 .ok_or(alloy_rlp::Error::Custom("cannot decode a receipt from empty bytes"))?;
307
308 match rlp_type.cmp(&alloy_rlp::EMPTY_LIST_CODE) {
309 Ordering::Less => {
310 let _header = Header::decode(buf)?;
312 let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom(
313 "typed receipt cannot be decoded from an empty slice",
314 ))?;
315 if receipt_type == EIP2930_TX_TYPE_ID {
316 buf.advance(1);
317 <ReceiptWithBloom as Decodable>::decode(buf)
318 .map(FoundryReceiptEnvelope::Eip2930)
319 } else if receipt_type == EIP1559_TX_TYPE_ID {
320 buf.advance(1);
321 <ReceiptWithBloom as Decodable>::decode(buf)
322 .map(FoundryReceiptEnvelope::Eip1559)
323 } else if receipt_type == EIP4844_TX_TYPE_ID {
324 buf.advance(1);
325 <ReceiptWithBloom as Decodable>::decode(buf)
326 .map(FoundryReceiptEnvelope::Eip4844)
327 } else if receipt_type == EIP7702_TX_TYPE_ID {
328 buf.advance(1);
329 <ReceiptWithBloom as Decodable>::decode(buf)
330 .map(FoundryReceiptEnvelope::Eip7702)
331 } else if receipt_type == DEPOSIT_TX_TYPE_ID {
332 buf.advance(1);
333 <OpDepositReceiptWithBloom as Decodable>::decode(buf)
334 .map(FoundryReceiptEnvelope::Deposit)
335 } else {
336 Err(alloy_rlp::Error::Custom("invalid receipt type"))
337 }
338 }
339 Ordering::Equal => {
340 Err(alloy_rlp::Error::Custom("an empty list is not a valid receipt encoding"))
341 }
342 Ordering::Greater => {
343 <ReceiptWithBloom as Decodable>::decode(buf).map(FoundryReceiptEnvelope::Legacy)
344 }
345 }
346 }
347}
348
349impl Typed2718 for FoundryReceiptEnvelope {
350 fn ty(&self) -> u8 {
351 match self {
352 Self::Legacy(_) => LEGACY_TX_TYPE_ID,
353 Self::Eip2930(_) => EIP2930_TX_TYPE_ID,
354 Self::Eip1559(_) => EIP1559_TX_TYPE_ID,
355 Self::Eip4844(_) => EIP4844_TX_TYPE_ID,
356 Self::Eip7702(_) => EIP7702_TX_TYPE_ID,
357 Self::Deposit(_) => DEPOSIT_TX_TYPE_ID,
358 }
359 }
360}
361
362impl Encodable2718 for FoundryReceiptEnvelope {
363 fn encode_2718_len(&self) -> usize {
364 match self {
365 Self::Legacy(r) => ReceiptEnvelope::Legacy(r.clone()).encode_2718_len(),
366 Self::Eip2930(r) => ReceiptEnvelope::Eip2930(r.clone()).encode_2718_len(),
367 Self::Eip1559(r) => ReceiptEnvelope::Eip1559(r.clone()).encode_2718_len(),
368 Self::Eip4844(r) => ReceiptEnvelope::Eip4844(r.clone()).encode_2718_len(),
369 Self::Eip7702(r) => 1 + r.length(),
370 Self::Deposit(r) => 1 + r.length(),
371 }
372 }
373
374 fn encode_2718(&self, out: &mut dyn BufMut) {
375 if let Some(ty) = self.type_flag() {
376 out.put_u8(ty);
377 }
378 match self {
379 Self::Legacy(r)
380 | Self::Eip2930(r)
381 | Self::Eip1559(r)
382 | Self::Eip4844(r)
383 | Self::Eip7702(r) => r.encode(out),
384 Self::Deposit(r) => r.encode(out),
385 }
386 }
387}
388
389impl Decodable2718 for FoundryReceiptEnvelope {
390 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
391 if ty == 0x7E {
392 return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?));
393 }
394 match ReceiptEnvelope::typed_decode(ty, buf)? {
395 ReceiptEnvelope::Eip2930(tx) => Ok(Self::Eip2930(tx)),
396 ReceiptEnvelope::Eip1559(tx) => Ok(Self::Eip1559(tx)),
397 ReceiptEnvelope::Eip4844(tx) => Ok(Self::Eip4844(tx)),
398 ReceiptEnvelope::Eip7702(tx) => Ok(Self::Eip7702(tx)),
399 _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))),
400 }
401 }
402
403 fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
404 match ReceiptEnvelope::fallback_decode(buf)? {
405 ReceiptEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
406 _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))),
407 }
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414 use alloy_primitives::{Address, B256, Bytes, LogData, hex};
415 use std::str::FromStr;
416
417 #[test]
418 fn encode_legacy_receipt() {
419 let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
420
421 let mut data = vec![];
422 let receipt = FoundryReceiptEnvelope::Legacy(ReceiptWithBloom {
423 receipt: Receipt {
424 status: false.into(),
425 cumulative_gas_used: 0x1,
426 logs: vec![Log {
427 address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
428 data: LogData::new_unchecked(
429 vec![
430 B256::from_str(
431 "000000000000000000000000000000000000000000000000000000000000dead",
432 )
433 .unwrap(),
434 B256::from_str(
435 "000000000000000000000000000000000000000000000000000000000000beef",
436 )
437 .unwrap(),
438 ],
439 Bytes::from_str("0100ff").unwrap(),
440 ),
441 }],
442 },
443 logs_bloom: [0; 256].into(),
444 });
445
446 receipt.encode(&mut data);
447
448 assert_eq!(receipt.length(), expected.len());
450 assert_eq!(data, expected);
451 }
452
453 #[test]
454 fn decode_legacy_receipt() {
455 let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
456
457 let expected = FoundryReceiptEnvelope::Legacy(ReceiptWithBloom {
458 receipt: Receipt {
459 status: false.into(),
460 cumulative_gas_used: 0x1,
461 logs: vec![Log {
462 address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
463 data: LogData::new_unchecked(
464 vec![
465 B256::from_str(
466 "000000000000000000000000000000000000000000000000000000000000dead",
467 )
468 .unwrap(),
469 B256::from_str(
470 "000000000000000000000000000000000000000000000000000000000000beef",
471 )
472 .unwrap(),
473 ],
474 Bytes::from_str("0100ff").unwrap(),
475 ),
476 }],
477 },
478 logs_bloom: [0; 256].into(),
479 });
480
481 let receipt = FoundryReceiptEnvelope::decode(&mut &data[..]).unwrap();
482
483 assert_eq!(receipt, expected);
484 }
485}