1use crate::FoundryTxType;
2use alloy_consensus::{
3 Eip658Value, Receipt, ReceiptEnvelope, ReceiptWithBloom, TxReceipt, Typed2718,
4};
5use alloy_network::{
6 AnyReceiptEnvelope,
7 eip2718::{
8 Decodable2718, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID,
9 EIP7702_TX_TYPE_ID, Eip2718Error, Encodable2718, LEGACY_TX_TYPE_ID,
10 },
11};
12use alloy_primitives::{Bloom, Log, TxHash, logs_bloom};
13use alloy_rlp::{BufMut, Decodable, Encodable, bytes};
14use alloy_rpc_types::{BlockNumHash, trace::otterscan::OtsReceipt};
15use serde::{Deserialize, Serialize};
16use tempo_primitives::TEMPO_TX_TYPE_ID;
17
18#[cfg(all(feature = "base", not(feature = "optimism")))]
19use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, OpDepositReceipt, OpDepositReceiptWithBloom};
20
21#[cfg(feature = "base")]
22use base_common_consensus::Eip8130Receipt;
23#[cfg(feature = "base")]
24use base_common_evm::EIP8130_TRANSACTION_TYPE;
25
26#[cfg(feature = "optimism")]
27use op_alloy_consensus::{
28 DEPOSIT_TX_TYPE_ID, OpDepositReceipt, OpDepositReceiptWithBloom, POST_EXEC_TX_TYPE_ID,
29};
30
31#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(tag = "type")]
33pub enum FoundryReceiptEnvelope<T = Log> {
34 #[serde(rename = "0x0", alias = "0x00")]
35 Legacy(ReceiptWithBloom<Receipt<T>>),
36 #[serde(rename = "0x1", alias = "0x01")]
37 Eip2930(ReceiptWithBloom<Receipt<T>>),
38 #[serde(rename = "0x2", alias = "0x02")]
39 Eip1559(ReceiptWithBloom<Receipt<T>>),
40 #[serde(rename = "0x3", alias = "0x03")]
41 Eip4844(ReceiptWithBloom<Receipt<T>>),
42 #[serde(rename = "0x4", alias = "0x04")]
43 Eip7702(ReceiptWithBloom<Receipt<T>>),
44 #[cfg(feature = "optimism")]
45 #[serde(rename = "0x7D", alias = "0x7d")]
46 PostExec(ReceiptWithBloom<Receipt<T>>),
47 #[cfg(any(feature = "base", feature = "optimism"))]
48 #[serde(rename = "0x7E", alias = "0x7e")]
49 Deposit(OpDepositReceiptWithBloom<T>),
50 #[cfg(feature = "base")]
51 #[serde(rename = "0x79")]
52 Eip8130(ReceiptWithBloom<Eip8130Receipt<T>>),
53 #[serde(rename = "0x76")]
54 Tempo(ReceiptWithBloom<Receipt<T>>),
55 #[serde(untagged)]
61 Unknown(AnyReceiptEnvelope<T>),
62}
63
64impl FoundryReceiptEnvelope<alloy_rpc_types::Log> {
65 pub fn from_parts(
67 status: bool,
68 cumulative_gas_used: u64,
69 logs: impl IntoIterator<Item = alloy_rpc_types::Log>,
70 tx_type: FoundryTxType,
71 #[cfg(feature = "base")] eip8130_phase_statuses: Vec<u8>,
72 #[cfg_attr(not(any(feature = "base", feature = "optimism")), allow(unused_variables))]
73 deposit_nonce: Option<u64>,
74 #[cfg_attr(not(any(feature = "base", feature = "optimism")), allow(unused_variables))]
75 deposit_receipt_version: Option<u64>,
76 ) -> Self {
77 let logs = logs.into_iter().collect::<Vec<_>>();
78 let logs_bloom = logs_bloom(logs.iter().map(|l| &l.inner));
79 let inner_receipt =
80 Receipt { status: Eip658Value::Eip658(status), cumulative_gas_used, logs };
81 match tx_type {
82 FoundryTxType::Legacy => {
83 Self::Legacy(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
84 }
85 FoundryTxType::Eip2930 => {
86 Self::Eip2930(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
87 }
88 FoundryTxType::Eip1559 => {
89 Self::Eip1559(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
90 }
91 FoundryTxType::Eip4844 => {
92 Self::Eip4844(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
93 }
94 FoundryTxType::Eip7702 => {
95 Self::Eip7702(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
96 }
97 #[cfg(feature = "optimism")]
98 FoundryTxType::PostExec => {
99 Self::PostExec(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
100 }
101 #[cfg(any(feature = "base", feature = "optimism"))]
102 FoundryTxType::Deposit => {
103 let inner = OpDepositReceiptWithBloom {
104 receipt: OpDepositReceipt {
105 inner: inner_receipt,
106 deposit_nonce,
107 deposit_receipt_version,
108 },
109 logs_bloom,
110 };
111 Self::Deposit(inner)
112 }
113 #[cfg(feature = "base")]
114 FoundryTxType::Eip8130 => Self::Eip8130(ReceiptWithBloom {
115 receipt: Eip8130Receipt::new(inner_receipt, eip8130_phase_statuses),
116 logs_bloom,
117 }),
118 FoundryTxType::Tempo => {
119 Self::Tempo(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
120 }
121 }
122 }
123}
124
125impl FoundryReceiptEnvelope<Log> {
126 pub fn convert_logs_rpc(
127 self,
128 block_numhash: BlockNumHash,
129 block_timestamp: u64,
130 transaction_hash: TxHash,
131 transaction_index: u64,
132 next_log_index: usize,
133 ) -> FoundryReceiptEnvelope<alloy_rpc_types::Log> {
134 let mut index = 0;
135 self.map_logs(|inner| {
136 let log = alloy_rpc_types::Log {
137 inner,
138 block_hash: Some(block_numhash.hash),
139 block_number: Some(block_numhash.number),
140 block_timestamp: Some(block_timestamp),
141 transaction_hash: Some(transaction_hash),
142 transaction_index: Some(transaction_index),
143 log_index: Some((next_log_index + index) as u64),
144 removed: false,
145 };
146 index += 1;
147 log
148 })
149 }
150}
151
152impl<T> FoundryReceiptEnvelope<T> {
153 #[cfg(any(feature = "base", feature = "optimism"))]
155 pub const fn is_deposit(&self) -> bool {
156 matches!(self, Self::Deposit(_))
157 }
158
159 #[cfg(feature = "optimism")]
161 pub const fn is_post_exec(&self) -> bool {
162 matches!(self, Self::PostExec(_))
163 }
164
165 #[cfg(feature = "base")]
167 pub const fn is_eip8130(&self) -> bool {
168 matches!(self, Self::Eip8130(_))
169 }
170
171 #[cfg(feature = "base")]
173 pub fn eip8130_phase_statuses(&self) -> &[u8] {
174 match self {
175 Self::Eip8130(receipt) => &receipt.receipt.phase_statuses,
176 _ => &[],
177 }
178 }
179
180 pub const fn is_tempo(&self) -> bool {
182 matches!(self, Self::Tempo(_))
183 }
184
185 pub const fn is_unknown(&self) -> bool {
187 matches!(self, Self::Unknown(_))
188 }
189
190 pub const fn ty(&self) -> u8 {
192 match self {
193 Self::Legacy(_) => LEGACY_TX_TYPE_ID,
194 Self::Eip2930(_) => EIP2930_TX_TYPE_ID,
195 Self::Eip1559(_) => EIP1559_TX_TYPE_ID,
196 Self::Eip4844(_) => EIP4844_TX_TYPE_ID,
197 Self::Eip7702(_) => EIP7702_TX_TYPE_ID,
198 #[cfg(feature = "optimism")]
199 Self::PostExec(_) => POST_EXEC_TX_TYPE_ID,
200 #[cfg(any(feature = "base", feature = "optimism"))]
201 Self::Deposit(_) => DEPOSIT_TX_TYPE_ID,
202 #[cfg(feature = "base")]
203 Self::Eip8130(_) => EIP8130_TRANSACTION_TYPE,
204 Self::Tempo(_) => TEMPO_TX_TYPE_ID,
205 Self::Unknown(r) => r.r#type,
206 }
207 }
208
209 pub const fn tx_type(&self) -> Option<FoundryTxType> {
211 Some(match self {
212 Self::Legacy(_) => FoundryTxType::Legacy,
213 Self::Eip2930(_) => FoundryTxType::Eip2930,
214 Self::Eip1559(_) => FoundryTxType::Eip1559,
215 Self::Eip4844(_) => FoundryTxType::Eip4844,
216 Self::Eip7702(_) => FoundryTxType::Eip7702,
217 #[cfg(feature = "optimism")]
218 Self::PostExec(_) => FoundryTxType::PostExec,
219 #[cfg(any(feature = "base", feature = "optimism"))]
220 Self::Deposit(_) => FoundryTxType::Deposit,
221 #[cfg(feature = "base")]
222 Self::Eip8130(_) => FoundryTxType::Eip8130,
223 Self::Tempo(_) => FoundryTxType::Tempo,
224 Self::Unknown(_) => return None,
225 })
226 }
227
228 pub const fn status(&self) -> bool {
230 self.as_receipt().status.coerce_status()
231 }
232
233 pub const fn cumulative_gas_used(&self) -> u64 {
235 self.as_receipt().cumulative_gas_used
236 }
237
238 pub fn map_logs<U>(self, f: impl FnMut(T) -> U) -> FoundryReceiptEnvelope<U> {
242 match self {
243 Self::Legacy(r) => FoundryReceiptEnvelope::Legacy(r.map_logs(f)),
244 Self::Eip2930(r) => FoundryReceiptEnvelope::Eip2930(r.map_logs(f)),
245 Self::Eip1559(r) => FoundryReceiptEnvelope::Eip1559(r.map_logs(f)),
246 Self::Eip4844(r) => FoundryReceiptEnvelope::Eip4844(r.map_logs(f)),
247 Self::Eip7702(r) => FoundryReceiptEnvelope::Eip7702(r.map_logs(f)),
248 #[cfg(feature = "optimism")]
249 Self::PostExec(r) => FoundryReceiptEnvelope::PostExec(r.map_logs(f)),
250 #[cfg(any(feature = "base", feature = "optimism"))]
251 Self::Deposit(r) => FoundryReceiptEnvelope::Deposit(
252 r.map_receipt(|r: OpDepositReceipt<T>| r.map_logs(f)),
253 ),
254 #[cfg(feature = "base")]
255 Self::Eip8130(r) => {
256 FoundryReceiptEnvelope::Eip8130(r.map_receipt(|r: Eip8130Receipt<T>| r.map_logs(f)))
257 }
258 Self::Tempo(r) => FoundryReceiptEnvelope::Tempo(r.map_logs(f)),
259 Self::Unknown(r) => FoundryReceiptEnvelope::Unknown(AnyReceiptEnvelope {
260 inner: r.inner.map_logs(f),
261 r#type: r.r#type,
262 }),
263 }
264 }
265
266 pub fn logs(&self) -> &[T] {
268 &self.as_receipt().logs
269 }
270
271 pub fn into_logs(self) -> Vec<T> {
273 self.into_receipt().logs
274 }
275
276 pub const fn logs_bloom(&self) -> &Bloom {
278 match self {
279 Self::Legacy(t) => &t.logs_bloom,
280 Self::Eip2930(t) => &t.logs_bloom,
281 Self::Eip1559(t) => &t.logs_bloom,
282 Self::Eip4844(t) => &t.logs_bloom,
283 Self::Eip7702(t) => &t.logs_bloom,
284 #[cfg(feature = "optimism")]
285 Self::PostExec(t) => &t.logs_bloom,
286 #[cfg(any(feature = "base", feature = "optimism"))]
287 Self::Deposit(t) => &t.logs_bloom,
288 #[cfg(feature = "base")]
289 Self::Eip8130(t) => &t.logs_bloom,
290 Self::Tempo(t) => &t.logs_bloom,
291 Self::Unknown(t) => &t.inner.logs_bloom,
292 }
293 }
294
295 pub fn into_receipt(self) -> Receipt<T> {
297 match self {
298 Self::Legacy(t)
299 | Self::Eip2930(t)
300 | Self::Eip1559(t)
301 | Self::Eip4844(t)
302 | Self::Eip7702(t)
303 | Self::Tempo(t) => t.receipt,
304 #[cfg(feature = "optimism")]
305 Self::PostExec(t) => t.receipt,
306 #[cfg(any(feature = "base", feature = "optimism"))]
307 Self::Deposit(t) => t.receipt.into_inner(),
308 #[cfg(feature = "base")]
309 Self::Eip8130(t) => t.receipt.into_inner(),
310 Self::Unknown(t) => t.inner.receipt,
311 }
312 }
313
314 pub const fn as_receipt(&self) -> &Receipt<T> {
316 match self {
317 Self::Legacy(t)
318 | Self::Eip2930(t)
319 | Self::Eip1559(t)
320 | Self::Eip4844(t)
321 | Self::Eip7702(t)
322 | Self::Tempo(t) => &t.receipt,
323 #[cfg(feature = "optimism")]
324 Self::PostExec(t) => &t.receipt,
325 #[cfg(any(feature = "base", feature = "optimism"))]
326 Self::Deposit(t) => &t.receipt.inner,
327 #[cfg(feature = "base")]
328 Self::Eip8130(t) => &t.receipt.inner,
329 Self::Unknown(t) => &t.inner.receipt,
330 }
331 }
332}
333
334impl<T> TxReceipt for FoundryReceiptEnvelope<T>
335where
336 T: Clone + core::fmt::Debug + PartialEq + Eq + Send + Sync,
337{
338 type Log = T;
339
340 fn status_or_post_state(&self) -> Eip658Value {
341 self.as_receipt().status
342 }
343
344 fn status(&self) -> bool {
345 self.status()
346 }
347
348 fn bloom(&self) -> Bloom {
350 *self.logs_bloom()
351 }
352
353 fn bloom_cheap(&self) -> Option<Bloom> {
354 Some(self.bloom())
355 }
356
357 fn cumulative_gas_used(&self) -> u64 {
359 self.cumulative_gas_used()
360 }
361
362 fn logs(&self) -> &[T] {
364 self.logs()
365 }
366}
367
368impl Encodable for FoundryReceiptEnvelope {
369 fn encode(&self, out: &mut dyn bytes::BufMut) {
370 self.network_encode(out);
371 }
372
373 fn length(&self) -> usize {
374 self.network_len()
375 }
376}
377
378impl Decodable for FoundryReceiptEnvelope {
379 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
380 Self::network_decode(buf).map_err(Into::into)
381 }
382}
383
384impl<T> Typed2718 for FoundryReceiptEnvelope<T> {
385 fn ty(&self) -> u8 {
386 self.ty()
387 }
388}
389
390impl Encodable2718 for FoundryReceiptEnvelope {
391 fn encode_2718_len(&self) -> usize {
392 match self {
393 Self::Legacy(r) => r.length(),
394 Self::Eip2930(r) => 1 + r.length(),
395 Self::Eip1559(r) => 1 + r.length(),
396 Self::Eip4844(r) => 1 + r.length(),
397 Self::Eip7702(r) => 1 + r.length(),
398 #[cfg(feature = "optimism")]
399 Self::PostExec(r) => 1 + r.length(),
400 #[cfg(any(feature = "base", feature = "optimism"))]
401 Self::Deposit(r) => 1 + r.length(),
402 #[cfg(feature = "base")]
403 Self::Eip8130(r) => 1 + r.length(),
404 Self::Tempo(r) => 1 + r.length(),
405 Self::Unknown(r) => r.rlp_payload_length(),
406 }
407 }
408
409 fn encode_2718(&self, out: &mut dyn BufMut) {
410 if let Some(ty) = self.type_flag() {
411 out.put_u8(ty);
412 }
413 match self {
414 Self::Legacy(r)
415 | Self::Eip2930(r)
416 | Self::Eip1559(r)
417 | Self::Eip4844(r)
418 | Self::Eip7702(r)
419 | Self::Tempo(r) => r.encode(out),
420 #[cfg(feature = "optimism")]
421 Self::PostExec(r) => r.encode(out),
422 #[cfg(any(feature = "base", feature = "optimism"))]
423 Self::Deposit(r) => r.encode(out),
424 #[cfg(feature = "base")]
425 Self::Eip8130(r) => r.encode(out),
426 Self::Unknown(r) => r.inner.encode(out),
427 }
428 }
429}
430
431impl Decodable2718 for FoundryReceiptEnvelope {
432 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
433 #[cfg(feature = "base")]
434 if ty == EIP8130_TRANSACTION_TYPE {
435 return Ok(Self::Eip8130(ReceiptWithBloom::decode(buf)?));
436 }
437 #[cfg(all(feature = "base", not(feature = "optimism")))]
438 if ty == DEPOSIT_TX_TYPE_ID {
439 return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?));
440 }
441 #[cfg(feature = "optimism")]
442 {
443 if ty == DEPOSIT_TX_TYPE_ID {
444 return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?));
445 }
446 if ty == POST_EXEC_TX_TYPE_ID {
447 return Ok(Self::PostExec(ReceiptWithBloom::decode(buf)?));
448 }
449 }
450 if ty == TEMPO_TX_TYPE_ID {
451 return Ok(Self::Tempo(ReceiptWithBloom::decode(buf)?));
452 }
453 match ty {
454 LEGACY_TX_TYPE_ID => Err(Eip2718Error::UnexpectedType(LEGACY_TX_TYPE_ID)),
455 EIP2930_TX_TYPE_ID | EIP1559_TX_TYPE_ID | EIP4844_TX_TYPE_ID | EIP7702_TX_TYPE_ID => {
456 match ReceiptEnvelope::typed_decode(ty, buf)? {
457 ReceiptEnvelope::Eip2930(tx) => Ok(Self::Eip2930(tx)),
458 ReceiptEnvelope::Eip1559(tx) => Ok(Self::Eip1559(tx)),
459 ReceiptEnvelope::Eip4844(tx) => Ok(Self::Eip4844(tx)),
460 ReceiptEnvelope::Eip7702(tx) => Ok(Self::Eip7702(tx)),
461 _ => {
462 Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type")))
463 }
464 }
465 }
466 _ => Ok(Self::Unknown(AnyReceiptEnvelope {
469 inner: ReceiptWithBloom::decode(buf)?,
470 r#type: ty,
471 })),
472 }
473 }
474
475 fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
476 match ReceiptEnvelope::fallback_decode(buf)? {
477 ReceiptEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
478 _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))),
479 }
480 }
481}
482
483impl From<FoundryReceiptEnvelope<alloy_rpc_types::Log>> for OtsReceipt {
484 fn from(receipt: FoundryReceiptEnvelope<alloy_rpc_types::Log>) -> Self {
485 Self {
486 status: receipt.status(),
487 cumulative_gas_used: receipt.cumulative_gas_used(),
488 logs: Some(receipt.logs().to_vec()),
489 logs_bloom: Some(receipt.logs_bloom().to_owned()),
490 r#type: receipt.ty(),
491 }
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498 use alloy_primitives::{Address, B256, Bytes, LogData, hex};
499 use std::str::FromStr;
500
501 fn receipt_for(tx_type: FoundryTxType) -> FoundryReceiptEnvelope {
502 FoundryReceiptEnvelope::<alloy_rpc_types::Log>::from_parts(
503 true,
504 0,
505 Vec::new(),
506 tx_type,
507 #[cfg(feature = "base")]
508 Vec::new(),
509 None,
510 None,
511 )
512 .map_logs(|log| log.inner)
513 }
514
515 #[test]
516 fn rlp_roundtrip() {
517 fn assert_roundtrip(receipt: FoundryReceiptEnvelope) {
518 let mut encoded = Vec::new();
519 receipt.encode(&mut encoded);
520 assert_eq!(encoded.len(), receipt.length());
521
522 let mut encoded = encoded.as_slice();
523 let decoded = FoundryReceiptEnvelope::decode(&mut encoded).unwrap();
524 assert_eq!(decoded, receipt);
525 assert!(encoded.is_empty());
526 }
527
528 for tx_type in [
529 FoundryTxType::Legacy,
530 FoundryTxType::Eip2930,
531 FoundryTxType::Eip1559,
532 FoundryTxType::Eip4844,
533 FoundryTxType::Eip7702,
534 FoundryTxType::Tempo,
535 #[cfg(feature = "base")]
536 FoundryTxType::Eip8130,
537 #[cfg(any(feature = "base", feature = "optimism"))]
538 FoundryTxType::Deposit,
539 ] {
540 assert_roundtrip(receipt_for(tx_type));
541 }
542 #[cfg(feature = "optimism")]
543 assert_roundtrip(receipt_for(FoundryTxType::PostExec));
544
545 let logs = vec![Log {
547 address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
548 data: LogData::new_unchecked(
549 vec![B256::repeat_byte(0x22)],
550 Bytes::from_static(&[0x01, 0x02, 0x03]),
551 ),
552 }];
553 let logs_bloom = logs_bloom(&logs);
554 let receipt = Receipt { status: false.into(), cumulative_gas_used: 0x2a, logs };
555 assert_roundtrip(FoundryReceiptEnvelope::Eip1559(ReceiptWithBloom {
556 receipt: receipt.clone(),
557 logs_bloom,
558 }));
559 #[cfg(any(feature = "base", feature = "optimism"))]
562 assert_roundtrip(FoundryReceiptEnvelope::Deposit(OpDepositReceiptWithBloom {
563 receipt: OpDepositReceipt {
564 inner: receipt,
565 deposit_nonce: Some(7),
566 deposit_receipt_version: Some(1),
567 },
568 logs_bloom,
569 }));
570 }
571
572 #[test]
573 fn encode_typed_receipt_uses_rlp_string() {
574 let receipt = receipt_for(FoundryTxType::Eip2930);
575 let mut encoded = Vec::new();
576 receipt.encode(&mut encoded);
577
578 assert_eq!(&encoded[..4], &[0xb9, 0x01, 0x0a, EIP2930_TX_TYPE_ID]);
580 }
581
582 const ARBITRUM_INTERNAL_TX_TYPE: u8 = 0x6a;
585
586 fn unknown_receipt(ty: u8) -> FoundryReceiptEnvelope {
587 let logs = vec![Log {
588 address: Address::from_str("0000000000000000000000000000000000000064").unwrap(),
589 data: LogData::new_unchecked(
590 vec![B256::repeat_byte(0x33)],
591 Bytes::from_static(&[0xaa, 0xbb]),
592 ),
593 }];
594 let logs_bloom = logs_bloom(&logs);
595 FoundryReceiptEnvelope::Unknown(AnyReceiptEnvelope {
596 inner: ReceiptWithBloom {
597 receipt: Receipt { status: true.into(), cumulative_gas_used: 0x1234, logs },
598 logs_bloom,
599 },
600 r#type: ty,
601 })
602 }
603
604 #[test]
605 fn unknown_receipt_roundtrips_and_keeps_type() {
606 let receipt = unknown_receipt(ARBITRUM_INTERNAL_TX_TYPE);
607
608 assert!(receipt.is_unknown());
609 assert_eq!(receipt.tx_type(), None);
610 assert_eq!(receipt.ty(), ARBITRUM_INTERNAL_TX_TYPE);
611 assert!(receipt.status());
612 assert_eq!(receipt.cumulative_gas_used(), 0x1234);
613 assert_eq!(receipt.logs().len(), 1);
614
615 let mut encoded_2718 = Vec::new();
616 receipt.encode_2718(&mut encoded_2718);
617 assert_eq!(encoded_2718[0], ARBITRUM_INTERNAL_TX_TYPE);
618 assert_eq!(encoded_2718.len(), receipt.encode_2718_len());
619 assert_eq!(FoundryReceiptEnvelope::decode_2718(&mut &encoded_2718[..]).unwrap(), receipt);
620
621 let mut encoded = Vec::new();
622 receipt.encode(&mut encoded);
623 assert_eq!(encoded.len(), receipt.length());
624 assert_eq!(FoundryReceiptEnvelope::decode(&mut &encoded[..]).unwrap(), receipt);
625 }
626
627 #[test]
628 fn unknown_receipt_serde_roundtrip() {
629 let receipt = unknown_receipt(ARBITRUM_INTERNAL_TX_TYPE);
630 let json = serde_json::to_value(&receipt).unwrap();
631 assert_eq!(json["type"], "0x6a");
632 assert_eq!(serde_json::from_value::<FoundryReceiptEnvelope>(json).unwrap(), receipt);
633
634 let known = receipt_for(FoundryTxType::Eip1559);
636 let json = serde_json::to_value(&known).unwrap();
637 let decoded = serde_json::from_value::<FoundryReceiptEnvelope>(json).unwrap();
638 assert_eq!(decoded, known);
639 assert!(!decoded.is_unknown());
640 }
641
642 #[test]
643 fn receipt_predicates() {
644 assert!(receipt_for(FoundryTxType::Legacy).is_legacy());
645 assert!(receipt_for(FoundryTxType::Eip2930).is_eip2930());
646 assert!(receipt_for(FoundryTxType::Eip1559).is_eip1559());
647 assert!(receipt_for(FoundryTxType::Eip4844).is_eip4844());
648 assert!(receipt_for(FoundryTxType::Eip7702).is_eip7702());
649 assert!(receipt_for(FoundryTxType::Tempo).is_tempo());
650 assert!(!receipt_for(FoundryTxType::Tempo).is_legacy());
651
652 #[cfg(any(feature = "base", feature = "optimism"))]
653 assert!(receipt_for(FoundryTxType::Deposit).is_deposit());
654 #[cfg(feature = "base")]
655 assert!(receipt_for(FoundryTxType::Eip8130).is_eip8130());
656 #[cfg(feature = "optimism")]
657 {
658 assert!(receipt_for(FoundryTxType::PostExec).is_post_exec());
659 }
660 }
661
662 #[cfg(feature = "base")]
663 #[test]
664 fn eip8130_receipt_preserves_phase_statuses_outside_consensus_encoding() {
665 let receipt = FoundryReceiptEnvelope::<alloy_rpc_types::Log>::from_parts(
666 false,
667 42_000,
668 Vec::new(),
669 FoundryTxType::Eip8130,
670 vec![0x01, 0x00],
671 None,
672 None,
673 )
674 .map_logs(|log| log.inner);
675 assert_eq!(receipt.eip8130_phase_statuses(), &[0x01, 0x00]);
676
677 let encoded = receipt.encoded_2718();
678 let decoded = FoundryReceiptEnvelope::decode_2718(&mut encoded.as_slice()).unwrap();
679 assert!(decoded.eip8130_phase_statuses().is_empty());
680 assert_eq!(decoded.encoded_2718(), encoded);
681 }
682
683 #[test]
684 fn encode_legacy_receipt() {
685 let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
686
687 let mut data = vec![];
688 let receipt = FoundryReceiptEnvelope::Legacy(ReceiptWithBloom {
689 receipt: Receipt {
690 status: false.into(),
691 cumulative_gas_used: 0x1,
692 logs: vec![Log {
693 address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
694 data: LogData::new_unchecked(
695 vec![
696 B256::from_str(
697 "000000000000000000000000000000000000000000000000000000000000dead",
698 )
699 .unwrap(),
700 B256::from_str(
701 "000000000000000000000000000000000000000000000000000000000000beef",
702 )
703 .unwrap(),
704 ],
705 Bytes::from_str("0100ff").unwrap(),
706 ),
707 }],
708 },
709 logs_bloom: [0; 256].into(),
710 });
711
712 receipt.encode(&mut data);
713
714 assert_eq!(receipt.length(), expected.len());
716 assert_eq!(data, expected);
717 }
718
719 #[test]
720 fn decode_legacy_receipt() {
721 let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
722
723 let expected = FoundryReceiptEnvelope::Legacy(ReceiptWithBloom {
724 receipt: Receipt {
725 status: false.into(),
726 cumulative_gas_used: 0x1,
727 logs: vec![Log {
728 address: Address::from_str("0000000000000000000000000000000000000011").unwrap(),
729 data: LogData::new_unchecked(
730 vec![
731 B256::from_str(
732 "000000000000000000000000000000000000000000000000000000000000dead",
733 )
734 .unwrap(),
735 B256::from_str(
736 "000000000000000000000000000000000000000000000000000000000000beef",
737 )
738 .unwrap(),
739 ],
740 Bytes::from_str("0100ff").unwrap(),
741 ),
742 }],
743 },
744 logs_bloom: [0; 256].into(),
745 });
746
747 let receipt = FoundryReceiptEnvelope::decode(&mut &data[..]).unwrap();
748
749 assert_eq!(receipt, expected);
750 }
751
752 #[test]
753 fn encode_tempo_receipt() {
754 let receipt = FoundryReceiptEnvelope::Tempo(ReceiptWithBloom {
755 receipt: Receipt {
756 status: true.into(),
757 cumulative_gas_used: 157716,
758 logs: vec![Log {
759 address: Address::from_str("20c0000000000000000000000000000000000000").unwrap(),
760 data: LogData::new_unchecked(
761 vec![
762 B256::from_str(
763 "8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
764 )
765 .unwrap(),
766 B256::from_str(
767 "000000000000000000000000566ff0f4a6114f8072ecdc8a7a8a13d8d0c6b45f",
768 )
769 .unwrap(),
770 B256::from_str(
771 "000000000000000000000000dec0000000000000000000000000000000000000",
772 )
773 .unwrap(),
774 ],
775 Bytes::from_str(
776 "0000000000000000000000000000000000000000000000000000000000989680",
777 )
778 .unwrap(),
779 ),
780 }],
781 },
782 logs_bloom: [0; 256].into(),
783 });
784
785 assert_eq!(receipt.tx_type(), Some(FoundryTxType::Tempo));
786 assert_eq!(receipt.ty(), TEMPO_TX_TYPE_ID);
787 assert!(receipt.status());
788 assert_eq!(receipt.cumulative_gas_used(), 157716);
789 assert_eq!(receipt.logs().len(), 1);
790
791 let mut encoded_2718 = Vec::new();
793 receipt.encode_2718(&mut encoded_2718);
794 assert_eq!(encoded_2718[0], TEMPO_TX_TYPE_ID);
795
796 let mut encoded = Vec::new();
798 receipt.encode(&mut encoded);
799 let decoded = FoundryReceiptEnvelope::decode(&mut &encoded[..]).unwrap();
800 assert_eq!(receipt, decoded);
801 }
802
803 #[test]
804 fn decode_tempo_receipt() {
805 let receipt = FoundryReceiptEnvelope::Tempo(ReceiptWithBloom {
806 receipt: Receipt { status: true.into(), cumulative_gas_used: 21000, logs: vec![] },
807 logs_bloom: [0; 256].into(),
808 });
809
810 let mut encoded = Vec::new();
812 receipt.encode_2718(&mut encoded);
813 assert_eq!(encoded[0], TEMPO_TX_TYPE_ID);
814
815 let decoded = FoundryReceiptEnvelope::decode_2718(&mut &encoded[..]).unwrap();
816 assert_eq!(receipt, decoded);
817 }
818
819 #[test]
820 fn tempo_receipt_from_parts() {
821 let receipt = FoundryReceiptEnvelope::<alloy_rpc_types::Log>::from_parts(
822 true,
823 100000,
824 vec![],
825 FoundryTxType::Tempo,
826 #[cfg(feature = "base")]
827 Vec::new(),
828 None,
829 None,
830 );
831
832 assert_eq!(receipt.tx_type(), Some(FoundryTxType::Tempo));
833 assert!(receipt.status());
834 assert_eq!(receipt.cumulative_gas_used(), 100000);
835 assert!(receipt.logs().is_empty());
836 #[cfg(any(feature = "base", feature = "optimism"))]
837 {
838 assert!(receipt.deposit_nonce().is_none());
839 assert!(receipt.deposit_receipt_version().is_none());
840 }
841 }
842
843 #[test]
844 fn tempo_receipt_map_logs() {
845 let receipt = FoundryReceiptEnvelope::Tempo(ReceiptWithBloom {
846 receipt: Receipt {
847 status: true.into(),
848 cumulative_gas_used: 21000,
849 logs: vec![Log {
850 address: Address::from_str("20c0000000000000000000000000000000000000").unwrap(),
851 data: LogData::new_unchecked(vec![], Bytes::default()),
852 }],
853 },
854 logs_bloom: [0; 256].into(),
855 });
856
857 let mapped = receipt.map_logs(|log| log);
859 assert_eq!(mapped.logs().len(), 1);
860 assert_eq!(mapped.tx_type(), Some(FoundryTxType::Tempo));
861 }
862}