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::Celo => Self::Unknown(AnyReceiptEnvelope {
119 inner: ReceiptWithBloom { receipt: inner_receipt, logs_bloom },
120 r#type: super::CIP64_TX_TYPE,
121 }),
122 FoundryTxType::Tempo => {
123 Self::Tempo(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
124 }
125 }
126 }
127}
128
129impl FoundryReceiptEnvelope<Log> {
130 pub fn convert_logs_rpc(
131 self,
132 block_numhash: BlockNumHash,
133 block_timestamp: u64,
134 transaction_hash: TxHash,
135 transaction_index: u64,
136 next_log_index: usize,
137 ) -> FoundryReceiptEnvelope<alloy_rpc_types::Log> {
138 let mut index = 0;
139 self.map_logs(|inner| {
140 let log = alloy_rpc_types::Log {
141 inner,
142 block_hash: Some(block_numhash.hash),
143 block_number: Some(block_numhash.number),
144 block_timestamp: Some(block_timestamp),
145 transaction_hash: Some(transaction_hash),
146 transaction_index: Some(transaction_index),
147 log_index: Some((next_log_index + index) as u64),
148 removed: false,
149 };
150 index += 1;
151 log
152 })
153 }
154}
155
156impl<T> FoundryReceiptEnvelope<T> {
157 #[cfg(any(feature = "base", feature = "optimism"))]
159 pub const fn is_deposit(&self) -> bool {
160 matches!(self, Self::Deposit(_))
161 }
162
163 #[cfg(feature = "optimism")]
165 pub const fn is_post_exec(&self) -> bool {
166 matches!(self, Self::PostExec(_))
167 }
168
169 #[cfg(feature = "base")]
171 pub const fn is_eip8130(&self) -> bool {
172 matches!(self, Self::Eip8130(_))
173 }
174
175 #[cfg(feature = "base")]
177 pub fn eip8130_phase_statuses(&self) -> &[u8] {
178 match self {
179 Self::Eip8130(receipt) => &receipt.receipt.phase_statuses,
180 _ => &[],
181 }
182 }
183
184 pub const fn is_tempo(&self) -> bool {
186 matches!(self, Self::Tempo(_))
187 }
188
189 pub const fn is_unknown(&self) -> bool {
191 matches!(self, Self::Unknown(_))
192 }
193
194 pub const fn ty(&self) -> u8 {
196 match self {
197 Self::Legacy(_) => LEGACY_TX_TYPE_ID,
198 Self::Eip2930(_) => EIP2930_TX_TYPE_ID,
199 Self::Eip1559(_) => EIP1559_TX_TYPE_ID,
200 Self::Eip4844(_) => EIP4844_TX_TYPE_ID,
201 Self::Eip7702(_) => EIP7702_TX_TYPE_ID,
202 #[cfg(feature = "optimism")]
203 Self::PostExec(_) => POST_EXEC_TX_TYPE_ID,
204 #[cfg(any(feature = "base", feature = "optimism"))]
205 Self::Deposit(_) => DEPOSIT_TX_TYPE_ID,
206 #[cfg(feature = "base")]
207 Self::Eip8130(_) => EIP8130_TRANSACTION_TYPE,
208 Self::Tempo(_) => TEMPO_TX_TYPE_ID,
209 Self::Unknown(r) => r.r#type,
210 }
211 }
212
213 pub const fn tx_type(&self) -> Option<FoundryTxType> {
215 Some(match self {
216 Self::Legacy(_) => FoundryTxType::Legacy,
217 Self::Eip2930(_) => FoundryTxType::Eip2930,
218 Self::Eip1559(_) => FoundryTxType::Eip1559,
219 Self::Eip4844(_) => FoundryTxType::Eip4844,
220 Self::Eip7702(_) => FoundryTxType::Eip7702,
221 #[cfg(feature = "optimism")]
222 Self::PostExec(_) => FoundryTxType::PostExec,
223 #[cfg(any(feature = "base", feature = "optimism"))]
224 Self::Deposit(_) => FoundryTxType::Deposit,
225 #[cfg(feature = "base")]
226 Self::Eip8130(_) => FoundryTxType::Eip8130,
227 Self::Tempo(_) => FoundryTxType::Tempo,
228 Self::Unknown(_) => return None,
229 })
230 }
231
232 pub const fn status(&self) -> bool {
234 self.as_receipt().status.coerce_status()
235 }
236
237 pub const fn cumulative_gas_used(&self) -> u64 {
239 self.as_receipt().cumulative_gas_used
240 }
241
242 pub fn map_logs<U>(self, f: impl FnMut(T) -> U) -> FoundryReceiptEnvelope<U> {
246 match self {
247 Self::Legacy(r) => FoundryReceiptEnvelope::Legacy(r.map_logs(f)),
248 Self::Eip2930(r) => FoundryReceiptEnvelope::Eip2930(r.map_logs(f)),
249 Self::Eip1559(r) => FoundryReceiptEnvelope::Eip1559(r.map_logs(f)),
250 Self::Eip4844(r) => FoundryReceiptEnvelope::Eip4844(r.map_logs(f)),
251 Self::Eip7702(r) => FoundryReceiptEnvelope::Eip7702(r.map_logs(f)),
252 #[cfg(feature = "optimism")]
253 Self::PostExec(r) => FoundryReceiptEnvelope::PostExec(r.map_logs(f)),
254 #[cfg(any(feature = "base", feature = "optimism"))]
255 Self::Deposit(r) => FoundryReceiptEnvelope::Deposit(
256 r.map_receipt(|r: OpDepositReceipt<T>| r.map_logs(f)),
257 ),
258 #[cfg(feature = "base")]
259 Self::Eip8130(r) => {
260 FoundryReceiptEnvelope::Eip8130(r.map_receipt(|r: Eip8130Receipt<T>| r.map_logs(f)))
261 }
262 Self::Tempo(r) => FoundryReceiptEnvelope::Tempo(r.map_logs(f)),
263 Self::Unknown(r) => FoundryReceiptEnvelope::Unknown(AnyReceiptEnvelope {
264 inner: r.inner.map_logs(f),
265 r#type: r.r#type,
266 }),
267 }
268 }
269
270 pub fn logs(&self) -> &[T] {
272 &self.as_receipt().logs
273 }
274
275 pub fn into_logs(self) -> Vec<T> {
277 self.into_receipt().logs
278 }
279
280 pub const fn logs_bloom(&self) -> &Bloom {
282 match self {
283 Self::Legacy(t) => t.bloom_ref(),
284 Self::Eip2930(t) => t.bloom_ref(),
285 Self::Eip1559(t) => t.bloom_ref(),
286 Self::Eip4844(t) => t.bloom_ref(),
287 Self::Eip7702(t) => t.bloom_ref(),
288 #[cfg(feature = "optimism")]
289 Self::PostExec(t) => t.bloom_ref(),
290 #[cfg(any(feature = "base", feature = "optimism"))]
291 Self::Deposit(t) => t.bloom_ref(),
292 #[cfg(feature = "base")]
293 Self::Eip8130(t) => t.bloom_ref(),
294 Self::Tempo(t) => t.bloom_ref(),
295 Self::Unknown(t) => &t.inner.logs_bloom,
296 }
297 }
298
299 pub fn into_receipt(self) -> Receipt<T> {
301 match self {
302 Self::Legacy(t)
303 | Self::Eip2930(t)
304 | Self::Eip1559(t)
305 | Self::Eip4844(t)
306 | Self::Eip7702(t)
307 | Self::Tempo(t) => t.receipt,
308 #[cfg(feature = "optimism")]
309 Self::PostExec(t) => t.receipt,
310 #[cfg(any(feature = "base", feature = "optimism"))]
311 Self::Deposit(t) => t.receipt.into_inner(),
312 #[cfg(feature = "base")]
313 Self::Eip8130(t) => t.receipt.into_inner(),
314 Self::Unknown(t) => t.inner.receipt,
315 }
316 }
317
318 pub const fn as_receipt(&self) -> &Receipt<T> {
320 match self {
321 Self::Legacy(t)
322 | Self::Eip2930(t)
323 | Self::Eip1559(t)
324 | Self::Eip4844(t)
325 | Self::Eip7702(t)
326 | Self::Tempo(t) => &t.receipt,
327 #[cfg(feature = "optimism")]
328 Self::PostExec(t) => &t.receipt,
329 #[cfg(any(feature = "base", feature = "optimism"))]
330 Self::Deposit(t) => &t.receipt.inner,
331 #[cfg(feature = "base")]
332 Self::Eip8130(t) => &t.receipt.inner,
333 Self::Unknown(t) => &t.inner.receipt,
334 }
335 }
336}
337
338impl<T> TxReceipt for FoundryReceiptEnvelope<T>
339where
340 T: Clone + core::fmt::Debug + PartialEq + Eq + Send + Sync,
341{
342 type Log = T;
343
344 fn status_or_post_state(&self) -> Eip658Value {
345 self.as_receipt().status
346 }
347
348 fn status(&self) -> bool {
349 self.status()
350 }
351
352 fn bloom(&self) -> Bloom {
354 *self.logs_bloom()
355 }
356
357 fn bloom_cheap(&self) -> Option<Bloom> {
358 Some(self.bloom())
359 }
360
361 fn cumulative_gas_used(&self) -> u64 {
363 self.cumulative_gas_used()
364 }
365
366 fn logs(&self) -> &[T] {
368 self.logs()
369 }
370}
371
372impl Encodable for FoundryReceiptEnvelope {
373 fn encode(&self, out: &mut dyn bytes::BufMut) {
374 self.network_encode(out);
375 }
376
377 fn length(&self) -> usize {
378 self.network_len()
379 }
380}
381
382impl Decodable for FoundryReceiptEnvelope {
383 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
384 Self::network_decode(buf).map_err(Into::into)
385 }
386}
387
388impl<T> Typed2718 for FoundryReceiptEnvelope<T> {
389 fn ty(&self) -> u8 {
390 self.ty()
391 }
392}
393
394impl Encodable2718 for FoundryReceiptEnvelope {
395 fn encode_2718_len(&self) -> usize {
396 match self {
397 Self::Legacy(r) => r.length(),
398 Self::Eip2930(r) => 1 + r.length(),
399 Self::Eip1559(r) => 1 + r.length(),
400 Self::Eip4844(r) => 1 + r.length(),
401 Self::Eip7702(r) => 1 + r.length(),
402 #[cfg(feature = "optimism")]
403 Self::PostExec(r) => 1 + r.length(),
404 #[cfg(any(feature = "base", feature = "optimism"))]
405 Self::Deposit(r) => 1 + r.length(),
406 #[cfg(feature = "base")]
407 Self::Eip8130(r) => 1 + r.length(),
408 Self::Tempo(r) => 1 + r.length(),
409 Self::Unknown(r) => r.rlp_payload_length(),
410 }
411 }
412
413 fn encode_2718(&self, out: &mut dyn BufMut) {
414 if let Some(ty) = self.type_flag() {
415 out.put_u8(ty);
416 }
417 match self {
418 Self::Legacy(r)
419 | Self::Eip2930(r)
420 | Self::Eip1559(r)
421 | Self::Eip4844(r)
422 | Self::Eip7702(r)
423 | Self::Tempo(r) => r.encode(out),
424 #[cfg(feature = "optimism")]
425 Self::PostExec(r) => r.encode(out),
426 #[cfg(any(feature = "base", feature = "optimism"))]
427 Self::Deposit(r) => r.encode(out),
428 #[cfg(feature = "base")]
429 Self::Eip8130(r) => r.encode(out),
430 Self::Unknown(r) => r.inner.encode(out),
431 }
432 }
433}
434
435impl Decodable2718 for FoundryReceiptEnvelope {
436 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
437 #[cfg(feature = "base")]
438 if ty == EIP8130_TRANSACTION_TYPE {
439 return Ok(Self::Eip8130(ReceiptWithBloom::decode(buf)?));
440 }
441 #[cfg(all(feature = "base", not(feature = "optimism")))]
442 if ty == DEPOSIT_TX_TYPE_ID {
443 return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?));
444 }
445 #[cfg(feature = "optimism")]
446 {
447 if ty == DEPOSIT_TX_TYPE_ID {
448 return Ok(Self::Deposit(OpDepositReceiptWithBloom::decode(buf)?));
449 }
450 if ty == POST_EXEC_TX_TYPE_ID {
451 return Ok(Self::PostExec(ReceiptWithBloom::decode(buf)?));
452 }
453 }
454 if ty == TEMPO_TX_TYPE_ID {
455 return Ok(Self::Tempo(ReceiptWithBloom::decode(buf)?));
456 }
457 match ty {
458 LEGACY_TX_TYPE_ID => Err(Eip2718Error::UnexpectedType(LEGACY_TX_TYPE_ID)),
459 EIP2930_TX_TYPE_ID | EIP1559_TX_TYPE_ID | EIP4844_TX_TYPE_ID | EIP7702_TX_TYPE_ID => {
460 match ReceiptEnvelope::typed_decode(ty, buf)? {
461 ReceiptEnvelope::Eip2930(tx) => Ok(Self::Eip2930(tx)),
462 ReceiptEnvelope::Eip1559(tx) => Ok(Self::Eip1559(tx)),
463 ReceiptEnvelope::Eip4844(tx) => Ok(Self::Eip4844(tx)),
464 ReceiptEnvelope::Eip7702(tx) => Ok(Self::Eip7702(tx)),
465 _ => {
466 Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type")))
467 }
468 }
469 }
470 _ => Ok(Self::Unknown(AnyReceiptEnvelope {
473 inner: ReceiptWithBloom::decode(buf)?,
474 r#type: ty,
475 })),
476 }
477 }
478
479 fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
480 match ReceiptEnvelope::fallback_decode(buf)? {
481 ReceiptEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
482 _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))),
483 }
484 }
485}
486
487impl From<FoundryReceiptEnvelope<alloy_rpc_types::Log>> for OtsReceipt {
488 fn from(receipt: FoundryReceiptEnvelope<alloy_rpc_types::Log>) -> Self {
489 Self {
490 status: receipt.status(),
491 cumulative_gas_used: receipt.cumulative_gas_used(),
492 logs: Some(receipt.logs().to_vec()),
493 logs_bloom: Some(receipt.logs_bloom().to_owned()),
494 r#type: receipt.ty(),
495 }
496 }
497}
498
499#[cfg(test)]
500mod tests {
501
502 use super::*;
503 use alloy_primitives::{Address, B256, Bytes, LogData, address, b256, hex};
504
505 fn receipt_for(tx_type: FoundryTxType) -> FoundryReceiptEnvelope {
506 FoundryReceiptEnvelope::<alloy_rpc_types::Log>::from_parts(
507 true,
508 0,
509 Vec::new(),
510 tx_type,
511 #[cfg(feature = "base")]
512 Vec::new(),
513 None,
514 None,
515 )
516 .map_logs(|log| log.inner)
517 }
518
519 #[test]
520 fn rlp_roundtrip() {
521 fn assert_roundtrip(receipt: FoundryReceiptEnvelope) {
522 let mut encoded = Vec::new();
523 receipt.encode(&mut encoded);
524 assert_eq!(encoded.len(), receipt.length());
525
526 let mut encoded = encoded.as_slice();
527 let decoded = FoundryReceiptEnvelope::decode(&mut encoded).unwrap();
528 assert_eq!(decoded, receipt);
529 assert!(encoded.is_empty());
530 }
531
532 for tx_type in [
533 FoundryTxType::Legacy,
534 FoundryTxType::Eip2930,
535 FoundryTxType::Eip1559,
536 FoundryTxType::Eip4844,
537 FoundryTxType::Eip7702,
538 FoundryTxType::Tempo,
539 #[cfg(feature = "base")]
540 FoundryTxType::Eip8130,
541 #[cfg(any(feature = "base", feature = "optimism"))]
542 FoundryTxType::Deposit,
543 ] {
544 assert_roundtrip(receipt_for(tx_type));
545 }
546 #[cfg(feature = "optimism")]
547 assert_roundtrip(receipt_for(FoundryTxType::PostExec));
548
549 let logs = vec![Log {
551 address: Address::with_last_byte(0x11),
552 data: LogData::new_unchecked(
553 vec![B256::repeat_byte(0x22)],
554 Bytes::from_static(&[0x01, 0x02, 0x03]),
555 ),
556 }];
557 let logs_bloom = logs_bloom(&logs);
558 let receipt = Receipt { status: false.into(), cumulative_gas_used: 0x2a, logs };
559 assert_roundtrip(FoundryReceiptEnvelope::Eip1559(ReceiptWithBloom {
560 receipt: receipt.clone(),
561 logs_bloom,
562 }));
563 #[cfg(any(feature = "base", feature = "optimism"))]
566 assert_roundtrip(FoundryReceiptEnvelope::Deposit(OpDepositReceiptWithBloom {
567 receipt: OpDepositReceipt {
568 inner: receipt,
569 deposit_nonce: Some(7),
570 deposit_receipt_version: Some(1),
571 },
572 logs_bloom,
573 }));
574 }
575
576 #[test]
577 fn encode_typed_receipt_uses_rlp_string() {
578 let receipt = receipt_for(FoundryTxType::Eip2930);
579 let mut encoded = Vec::new();
580 receipt.encode(&mut encoded);
581
582 assert_eq!(&encoded[..4], &[0xb9, 0x01, 0x0a, EIP2930_TX_TYPE_ID]);
584 }
585
586 const ARBITRUM_INTERNAL_TX_TYPE: u8 = 0x6a;
589
590 fn unknown_receipt(ty: u8) -> FoundryReceiptEnvelope {
591 let logs = vec![Log {
592 address: Address::with_last_byte(0x64),
593 data: LogData::new_unchecked(
594 vec![B256::repeat_byte(0x33)],
595 Bytes::from_static(&[0xaa, 0xbb]),
596 ),
597 }];
598 let logs_bloom = logs_bloom(&logs);
599 FoundryReceiptEnvelope::Unknown(AnyReceiptEnvelope {
600 inner: ReceiptWithBloom {
601 receipt: Receipt { status: true.into(), cumulative_gas_used: 0x1234, logs },
602 logs_bloom,
603 },
604 r#type: ty,
605 })
606 }
607
608 #[test]
609 fn unknown_receipt_roundtrips_and_keeps_type() {
610 let receipt = unknown_receipt(ARBITRUM_INTERNAL_TX_TYPE);
611
612 assert!(receipt.is_unknown());
613 assert_eq!(receipt.tx_type(), None);
614 assert_eq!(receipt.ty(), ARBITRUM_INTERNAL_TX_TYPE);
615 assert!(receipt.status());
616 assert_eq!(receipt.cumulative_gas_used(), 0x1234);
617 assert_eq!(receipt.logs().len(), 1);
618
619 let mut encoded_2718 = Vec::new();
620 receipt.encode_2718(&mut encoded_2718);
621 assert_eq!(encoded_2718[0], ARBITRUM_INTERNAL_TX_TYPE);
622 assert_eq!(encoded_2718.len(), receipt.encode_2718_len());
623 assert_eq!(FoundryReceiptEnvelope::decode_2718(&mut &encoded_2718[..]).unwrap(), receipt);
624
625 let mut encoded = Vec::new();
626 receipt.encode(&mut encoded);
627 assert_eq!(encoded.len(), receipt.length());
628 assert_eq!(FoundryReceiptEnvelope::decode(&mut &encoded[..]).unwrap(), receipt);
629 }
630
631 #[test]
632 fn unknown_receipt_serde_roundtrip() {
633 let receipt = unknown_receipt(ARBITRUM_INTERNAL_TX_TYPE);
634 let json = serde_json::to_value(&receipt).unwrap();
635 assert_eq!(json["type"], "0x6a");
636 assert_eq!(serde_json::from_value::<FoundryReceiptEnvelope>(json).unwrap(), receipt);
637
638 let known = receipt_for(FoundryTxType::Eip1559);
640 let json = serde_json::to_value(&known).unwrap();
641 let decoded = serde_json::from_value::<FoundryReceiptEnvelope>(json).unwrap();
642 assert_eq!(decoded, known);
643 assert!(!decoded.is_unknown());
644 }
645
646 #[test]
647 fn receipt_predicates() {
648 assert!(receipt_for(FoundryTxType::Legacy).is_legacy());
649 assert!(receipt_for(FoundryTxType::Eip2930).is_eip2930());
650 assert!(receipt_for(FoundryTxType::Eip1559).is_eip1559());
651 assert!(receipt_for(FoundryTxType::Eip4844).is_eip4844());
652 assert!(receipt_for(FoundryTxType::Eip7702).is_eip7702());
653 assert!(receipt_for(FoundryTxType::Tempo).is_tempo());
654 assert!(!receipt_for(FoundryTxType::Tempo).is_legacy());
655
656 #[cfg(any(feature = "base", feature = "optimism"))]
657 assert!(receipt_for(FoundryTxType::Deposit).is_deposit());
658 #[cfg(feature = "base")]
659 assert!(receipt_for(FoundryTxType::Eip8130).is_eip8130());
660 #[cfg(feature = "optimism")]
661 {
662 assert!(receipt_for(FoundryTxType::PostExec).is_post_exec());
663 }
664 }
665
666 #[cfg(feature = "base")]
667 #[test]
668 fn eip8130_receipt_preserves_phase_statuses_outside_consensus_encoding() {
669 let receipt = FoundryReceiptEnvelope::<alloy_rpc_types::Log>::from_parts(
670 false,
671 42_000,
672 Vec::new(),
673 FoundryTxType::Eip8130,
674 vec![0x01, 0x00],
675 None,
676 None,
677 )
678 .map_logs(|log| log.inner);
679 assert_eq!(receipt.eip8130_phase_statuses(), &[0x01, 0x00]);
680
681 let encoded = receipt.encoded_2718();
682 let decoded = FoundryReceiptEnvelope::decode_2718(&mut encoded.as_slice()).unwrap();
683 assert!(decoded.eip8130_phase_statuses().is_empty());
684 assert_eq!(decoded.encoded_2718(), encoded);
685 }
686
687 #[test]
688 fn encode_legacy_receipt() {
689 let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
690
691 let mut data = vec![];
692 let receipt = FoundryReceiptEnvelope::Legacy(ReceiptWithBloom {
693 receipt: Receipt {
694 status: false.into(),
695 cumulative_gas_used: 0x1,
696 logs: vec![Log {
697 address: Address::with_last_byte(0x11),
698 data: LogData::new_unchecked(
699 vec![
700 b256!(
701 "000000000000000000000000000000000000000000000000000000000000dead"
702 ),
703 b256!(
704 "000000000000000000000000000000000000000000000000000000000000beef"
705 ),
706 ],
707 alloy_primitives::bytes!("0100ff"),
708 ),
709 }],
710 },
711 logs_bloom: Bloom::ZERO,
712 });
713
714 receipt.encode(&mut data);
715
716 assert_eq!(receipt.length(), expected.len());
718 assert_eq!(data, expected);
719 }
720
721 #[test]
722 fn decode_legacy_receipt() {
723 let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap();
724
725 let expected = FoundryReceiptEnvelope::Legacy(ReceiptWithBloom {
726 receipt: Receipt {
727 status: false.into(),
728 cumulative_gas_used: 0x1,
729 logs: vec![Log {
730 address: Address::with_last_byte(0x11),
731 data: LogData::new_unchecked(
732 vec![
733 b256!(
734 "000000000000000000000000000000000000000000000000000000000000dead"
735 ),
736 b256!(
737 "000000000000000000000000000000000000000000000000000000000000beef"
738 ),
739 ],
740 alloy_primitives::bytes!("0100ff"),
741 ),
742 }],
743 },
744 logs_bloom: Bloom::ZERO,
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!("20c0000000000000000000000000000000000000"),
760 data: LogData::new_unchecked(
761 vec![
762 b256!(
763 "8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"
764 ),
765 b256!(
766 "000000000000000000000000566ff0f4a6114f8072ecdc8a7a8a13d8d0c6b45f"
767 ),
768 b256!(
769 "000000000000000000000000dec0000000000000000000000000000000000000"
770 ),
771 ],
772 alloy_primitives::bytes!(
773 "0000000000000000000000000000000000000000000000000000000000989680"
774 ),
775 ),
776 }],
777 },
778 logs_bloom: Bloom::ZERO,
779 });
780
781 assert_eq!(receipt.tx_type(), Some(FoundryTxType::Tempo));
782 assert_eq!(receipt.ty(), TEMPO_TX_TYPE_ID);
783 assert!(receipt.status());
784 assert_eq!(receipt.cumulative_gas_used(), 157716);
785 assert_eq!(receipt.logs().len(), 1);
786
787 let mut encoded_2718 = Vec::new();
789 receipt.encode_2718(&mut encoded_2718);
790 assert_eq!(encoded_2718[0], TEMPO_TX_TYPE_ID);
791
792 let mut encoded = Vec::new();
794 receipt.encode(&mut encoded);
795 let decoded = FoundryReceiptEnvelope::decode(&mut &encoded[..]).unwrap();
796 assert_eq!(receipt, decoded);
797 }
798
799 #[test]
800 fn decode_tempo_receipt() {
801 let receipt = FoundryReceiptEnvelope::Tempo(ReceiptWithBloom {
802 receipt: Receipt { status: true.into(), cumulative_gas_used: 21000, logs: vec![] },
803 logs_bloom: Bloom::ZERO,
804 });
805
806 let mut encoded = Vec::new();
808 receipt.encode_2718(&mut encoded);
809 assert_eq!(encoded[0], TEMPO_TX_TYPE_ID);
810
811 let decoded = FoundryReceiptEnvelope::decode_2718(&mut &encoded[..]).unwrap();
812 assert_eq!(receipt, decoded);
813 }
814
815 #[test]
816 fn tempo_receipt_from_parts() {
817 let receipt = FoundryReceiptEnvelope::<alloy_rpc_types::Log>::from_parts(
818 true,
819 100000,
820 vec![],
821 FoundryTxType::Tempo,
822 #[cfg(feature = "base")]
823 Vec::new(),
824 None,
825 None,
826 );
827
828 assert_eq!(receipt.tx_type(), Some(FoundryTxType::Tempo));
829 assert!(receipt.status());
830 assert_eq!(receipt.cumulative_gas_used(), 100000);
831 assert!(receipt.logs().is_empty());
832 #[cfg(any(feature = "base", feature = "optimism"))]
833 {
834 assert!(receipt.deposit_nonce().is_none());
835 assert!(receipt.deposit_receipt_version().is_none());
836 }
837 }
838
839 #[test]
840 fn tempo_receipt_map_logs() {
841 let receipt = FoundryReceiptEnvelope::Tempo(ReceiptWithBloom {
842 receipt: Receipt {
843 status: true.into(),
844 cumulative_gas_used: 21000,
845 logs: vec![Log {
846 address: address!("20c0000000000000000000000000000000000000"),
847 data: LogData::new_unchecked(vec![], Bytes::default()),
848 }],
849 },
850 logs_bloom: Bloom::ZERO,
851 });
852
853 let mapped = receipt.map_logs(|log| log);
855 assert_eq!(mapped.logs().len(), 1);
856 assert_eq!(mapped.tx_type(), Some(FoundryTxType::Tempo));
857 }
858}