1use crate::receipt::{
2 Eip2718EncodableReceipt, Eip658Value, RlpDecodableReceipt, RlpEncodableReceipt, TxReceipt,
3};
4use alloc::{vec, vec::Vec};
5use alloy_eips::{eip2718::Encodable2718, Typed2718};
6use alloy_primitives::{Bloom, Log};
7use alloy_rlp::{BufMut, Decodable, Encodable, Header};
8use core::fmt;
9
10#[derive(Clone, Debug, Default, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
13#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
14#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
15#[doc(alias = "TransactionReceipt", alias = "TxReceipt")]
16pub struct Receipt<T = Log> {
17 #[cfg_attr(feature = "serde", serde(flatten))]
21 pub status: Eip658Value,
22 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
24 pub cumulative_gas_used: u64,
25 pub logs: Vec<T>,
27}
28
29impl<T> Receipt<T> {
30 pub fn map_logs<U>(self, f: impl FnMut(T) -> U) -> Receipt<U> {
34 let Self { status, cumulative_gas_used, logs } = self;
35 Receipt { status, cumulative_gas_used, logs: logs.into_iter().map(f).collect() }
36 }
37}
38
39impl<T> Receipt<T>
40where
41 T: AsRef<Log>,
42{
43 pub fn bloom_slow(&self) -> Bloom {
46 self.logs.iter().map(AsRef::as_ref).collect()
47 }
48
49 pub fn with_bloom(self) -> ReceiptWithBloom<Self> {
52 ReceiptWithBloom { logs_bloom: self.bloom_slow(), receipt: self }
53 }
54}
55
56impl<T> Receipt<T>
57where
58 T: Into<Log>,
59{
60 pub fn into_primitives_receipt(self) -> Receipt<Log> {
66 self.map_logs(Into::into)
67 }
68}
69
70impl<T> TxReceipt for Receipt<T>
71where
72 T: AsRef<Log> + Clone + fmt::Debug + PartialEq + Eq + Send + Sync,
73{
74 type Log = T;
75
76 fn status_or_post_state(&self) -> Eip658Value {
77 self.status
78 }
79
80 fn status(&self) -> bool {
81 self.status.coerce_status()
82 }
83
84 fn bloom(&self) -> Bloom {
85 self.bloom_slow()
86 }
87
88 fn cumulative_gas_used(&self) -> u64 {
89 self.cumulative_gas_used
90 }
91
92 fn logs(&self) -> &[Self::Log] {
93 &self.logs
94 }
95
96 fn into_logs(self) -> Vec<Self::Log>
97 where
98 Self::Log: Clone,
99 {
100 self.logs
101 }
102}
103
104impl<T: Encodable> Receipt<T> {
105 pub fn rlp_encoded_fields_length_with_bloom(&self, bloom: &Bloom) -> usize {
107 self.status.length()
108 + self.cumulative_gas_used.length()
109 + bloom.length()
110 + self.logs.length()
111 }
112
113 pub fn rlp_encode_fields_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
115 self.status.encode(out);
116 self.cumulative_gas_used.encode(out);
117 bloom.encode(out);
118 self.logs.encode(out);
119 }
120
121 pub fn rlp_header_with_bloom(&self, bloom: &Bloom) -> Header {
123 Header { list: true, payload_length: self.rlp_encoded_fields_length_with_bloom(bloom) }
124 }
125}
126
127impl<T: Encodable> RlpEncodableReceipt for Receipt<T> {
128 fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
129 self.rlp_header_with_bloom(bloom).length_with_payload()
130 }
131
132 fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
133 self.rlp_header_with_bloom(bloom).encode(out);
134 self.rlp_encode_fields_with_bloom(bloom, out);
135 }
136}
137
138impl<T: Decodable> Receipt<T> {
139 pub fn rlp_decode_fields_with_bloom(
143 buf: &mut &[u8],
144 ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
145 let status = Decodable::decode(buf)?;
146 let cumulative_gas_used = Decodable::decode(buf)?;
147 let logs_bloom = Decodable::decode(buf)?;
148 let logs = Decodable::decode(buf)?;
149
150 Ok(ReceiptWithBloom { receipt: Self { status, cumulative_gas_used, logs }, logs_bloom })
151 }
152}
153
154impl<T: Decodable> RlpDecodableReceipt for Receipt<T> {
155 fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
156 let header = Header::decode(buf)?;
157 if !header.list {
158 return Err(alloy_rlp::Error::UnexpectedString);
159 }
160
161 let remaining = buf.len();
162
163 let this = Self::rlp_decode_fields_with_bloom(buf)?;
164
165 if buf.len() + header.payload_length != remaining {
166 return Err(alloy_rlp::Error::UnexpectedLength);
167 }
168
169 Ok(this)
170 }
171}
172
173impl<T> From<ReceiptWithBloom<Self>> for Receipt<T> {
174 fn from(receipt_with_bloom: ReceiptWithBloom<Self>) -> Self {
176 receipt_with_bloom.receipt
177 }
178}
179
180#[derive(
182 Clone,
183 Debug,
184 PartialEq,
185 Eq,
186 derive_more::Deref,
187 derive_more::DerefMut,
188 derive_more::From,
189 derive_more::IntoIterator,
190)]
191#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
192pub struct Receipts<T> {
193 pub receipt_vec: Vec<Vec<T>>,
195}
196
197impl<T> Receipts<T> {
198 pub fn len(&self) -> usize {
200 self.receipt_vec.len()
201 }
202
203 pub fn is_empty(&self) -> bool {
205 self.receipt_vec.is_empty()
206 }
207
208 pub fn push(&mut self, receipts: Vec<T>) {
210 self.receipt_vec.push(receipts);
211 }
212}
213
214impl<T> From<Vec<T>> for Receipts<T> {
215 fn from(block_receipts: Vec<T>) -> Self {
216 Self { receipt_vec: vec![block_receipts] }
217 }
218}
219
220impl<T> FromIterator<Vec<T>> for Receipts<T> {
221 fn from_iter<I: IntoIterator<Item = Vec<T>>>(iter: I) -> Self {
222 Self { receipt_vec: iter.into_iter().collect() }
223 }
224}
225
226impl<T: Encodable> Encodable for Receipts<T> {
227 fn encode(&self, out: &mut dyn BufMut) {
228 self.receipt_vec.encode(out)
229 }
230
231 fn length(&self) -> usize {
232 self.receipt_vec.length()
233 }
234}
235
236impl<T: Decodable> Decodable for Receipts<T> {
237 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
238 Ok(Self { receipt_vec: Decodable::decode(buf)? })
239 }
240}
241
242impl<T> Default for Receipts<T> {
243 fn default() -> Self {
244 Self { receipt_vec: Default::default() }
245 }
246}
247
248#[derive(Clone, Debug, Default, PartialEq, Eq)]
255#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
256#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
257#[doc(alias = "TransactionReceiptWithBloom", alias = "TxReceiptWithBloom")]
258pub struct ReceiptWithBloom<T = Receipt<Log>> {
259 #[cfg_attr(feature = "serde", serde(flatten))]
260 pub receipt: T,
262 pub logs_bloom: Bloom,
264}
265
266impl<R> TxReceipt for ReceiptWithBloom<R>
267where
268 R: TxReceipt,
269{
270 type Log = R::Log;
271
272 fn status_or_post_state(&self) -> Eip658Value {
273 self.receipt.status_or_post_state()
274 }
275
276 fn status(&self) -> bool {
277 self.receipt.status()
278 }
279
280 fn bloom(&self) -> Bloom {
281 self.logs_bloom
282 }
283
284 fn bloom_cheap(&self) -> Option<Bloom> {
285 Some(self.logs_bloom)
286 }
287
288 fn cumulative_gas_used(&self) -> u64 {
289 self.receipt.cumulative_gas_used()
290 }
291
292 fn logs(&self) -> &[Self::Log] {
293 self.receipt.logs()
294 }
295}
296
297impl<R> From<R> for ReceiptWithBloom<R>
298where
299 R: TxReceipt,
300{
301 fn from(receipt: R) -> Self {
302 let logs_bloom = receipt.bloom();
303 Self { logs_bloom, receipt }
304 }
305}
306
307impl<R> ReceiptWithBloom<R> {
308 pub fn map_receipt<U>(self, f: impl FnOnce(R) -> U) -> ReceiptWithBloom<U> {
312 let Self { receipt, logs_bloom } = self;
313 ReceiptWithBloom { receipt: f(receipt), logs_bloom }
314 }
315
316 pub const fn new(receipt: R, logs_bloom: Bloom) -> Self {
318 Self { receipt, logs_bloom }
319 }
320
321 pub fn into_components(self) -> (R, Bloom) {
323 (self.receipt, self.logs_bloom)
324 }
325
326 pub const fn bloom_ref(&self) -> &Bloom {
328 &self.logs_bloom
329 }
330}
331
332impl<L> ReceiptWithBloom<Receipt<L>> {
333 pub fn map_logs<U>(self, f: impl FnMut(L) -> U) -> ReceiptWithBloom<Receipt<U>> {
337 let Self { receipt, logs_bloom } = self;
338 ReceiptWithBloom { receipt: receipt.map_logs(f), logs_bloom }
339 }
340
341 pub fn into_primitives_receipt(self) -> ReceiptWithBloom<Receipt<Log>>
347 where
348 L: Into<Log>,
349 {
350 self.map_logs(Into::into)
351 }
352}
353
354impl<R: RlpEncodableReceipt> Encodable for ReceiptWithBloom<R> {
355 fn encode(&self, out: &mut dyn BufMut) {
356 self.receipt.rlp_encode_with_bloom(&self.logs_bloom, out);
357 }
358
359 fn length(&self) -> usize {
360 self.receipt.rlp_encoded_length_with_bloom(&self.logs_bloom)
361 }
362}
363
364impl<R: RlpDecodableReceipt> Decodable for ReceiptWithBloom<R> {
365 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
366 R::rlp_decode_with_bloom(buf)
367 }
368}
369
370impl<R: Typed2718> Typed2718 for ReceiptWithBloom<R> {
371 fn ty(&self) -> u8 {
372 self.receipt.ty()
373 }
374}
375
376impl<R> Encodable2718 for ReceiptWithBloom<R>
377where
378 R: Eip2718EncodableReceipt + Send + Sync,
379{
380 fn encode_2718_len(&self) -> usize {
381 self.receipt.eip2718_encoded_length_with_bloom(&self.logs_bloom)
382 }
383
384 fn encode_2718(&self, out: &mut dyn BufMut) {
385 self.receipt.eip2718_encode_with_bloom(&self.logs_bloom, out);
386 }
387}
388
389#[cfg(any(test, feature = "arbitrary"))]
390impl<'a, R> arbitrary::Arbitrary<'a> for ReceiptWithBloom<R>
391where
392 R: arbitrary::Arbitrary<'a>,
393{
394 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
395 Ok(Self { receipt: R::arbitrary(u)?, logs_bloom: Bloom::arbitrary(u)? })
396 }
397}
398
399#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
400pub(crate) mod serde_bincode_compat {
401 use alloc::borrow::Cow;
402 use serde::{Deserialize, Deserializer, Serialize, Serializer};
403 use serde_with::{DeserializeAs, SerializeAs};
404
405 #[derive(Debug, Serialize, Deserialize)]
421 pub struct Receipt<'a, T: Clone = alloy_primitives::Log> {
422 logs: Cow<'a, [T]>,
423 status: bool,
424 cumulative_gas_used: u64,
425 }
426
427 impl<'a, T: Clone> From<&'a super::Receipt<T>> for Receipt<'a, T> {
428 fn from(value: &'a super::Receipt<T>) -> Self {
429 Self {
430 logs: Cow::Borrowed(&value.logs),
431 status: value.status.coerce_status(),
433 cumulative_gas_used: value.cumulative_gas_used,
434 }
435 }
436 }
437
438 impl<'a, T: Clone> From<Receipt<'a, T>> for super::Receipt<T> {
439 fn from(value: Receipt<'a, T>) -> Self {
440 Self {
441 status: value.status.into(),
442 cumulative_gas_used: value.cumulative_gas_used,
443 logs: value.logs.into_owned(),
444 }
445 }
446 }
447
448 impl<T: Serialize + Clone> SerializeAs<super::Receipt<T>> for Receipt<'_, T> {
449 fn serialize_as<S>(source: &super::Receipt<T>, serializer: S) -> Result<S::Ok, S::Error>
450 where
451 S: Serializer,
452 {
453 Receipt::<'_, T>::from(source).serialize(serializer)
454 }
455 }
456
457 impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::Receipt<T>> for Receipt<'de, T> {
458 fn deserialize_as<D>(deserializer: D) -> Result<super::Receipt<T>, D::Error>
459 where
460 D: Deserializer<'de>,
461 {
462 Receipt::<'_, T>::deserialize(deserializer).map(Into::into)
463 }
464 }
465
466 #[cfg(test)]
467 mod tests {
468 use super::super::{serde_bincode_compat, Receipt};
469 use alloy_primitives::Log;
470 use arbitrary::Arbitrary;
471 use bincode::config;
472 use rand::Rng;
473 use serde::{de::DeserializeOwned, Deserialize, Serialize};
474 use serde_with::serde_as;
475
476 #[test]
477 fn test_receipt_bincode_roundtrip() {
478 #[serde_as]
479 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
480 struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
481 #[serde_as(as = "serde_bincode_compat::Receipt<'_,T>")]
482 receipt: Receipt<T>,
483 }
484
485 let mut bytes = [0u8; 1024];
486 rand::thread_rng().fill(bytes.as_mut_slice());
487 let mut data = Data {
488 receipt: Receipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(),
489 };
490 data.receipt.status = data.receipt.status.coerce_status().into();
492
493 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
494 let (decoded, _) =
495 bincode::serde::decode_from_slice::<Data<Log>, _>(&encoded, config::legacy())
496 .unwrap();
497 assert_eq!(decoded, data);
498 }
499 }
500}
501
502#[cfg(test)]
503mod test {
504 use super::*;
505 use crate::ReceiptEnvelope;
506 use alloy_rlp::{Decodable, Encodable};
507
508 const fn assert_tx_receipt<T: TxReceipt>() {}
509
510 #[test]
511 const fn assert_receipt() {
512 assert_tx_receipt::<Receipt>();
513 assert_tx_receipt::<ReceiptWithBloom<Receipt>>();
514 }
515
516 #[cfg(feature = "serde")]
517 #[test]
518 fn root_vs_status() {
519 let receipt = super::Receipt::<()> {
520 status: super::Eip658Value::Eip658(true),
521 cumulative_gas_used: 0,
522 logs: Vec::new(),
523 };
524
525 let json = serde_json::to_string(&receipt).unwrap();
526 assert_eq!(json, r#"{"status":"0x1","cumulativeGasUsed":"0x0","logs":[]}"#);
527
528 let receipt = super::Receipt::<()> {
529 status: super::Eip658Value::PostState(Default::default()),
530 cumulative_gas_used: 0,
531 logs: Vec::new(),
532 };
533
534 let json = serde_json::to_string(&receipt).unwrap();
535 assert_eq!(
536 json,
537 r#"{"root":"0x0000000000000000000000000000000000000000000000000000000000000000","cumulativeGasUsed":"0x0","logs":[]}"#
538 );
539 }
540
541 #[cfg(feature = "serde")]
542 #[test]
543 fn deser_pre658() {
544 use alloy_primitives::b256;
545
546 let json = r#"{"root":"0x284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10","cumulativeGasUsed":"0x0","logs":[]}"#;
547
548 let receipt: super::Receipt<()> = serde_json::from_str(json).unwrap();
549
550 assert_eq!(
551 receipt.status,
552 super::Eip658Value::PostState(b256!(
553 "284d35bf53b82ef480ab4208527325477439c64fb90ef518450f05ee151c8e10"
554 ))
555 );
556 }
557
558 #[test]
559 fn roundtrip_encodable_eip1559() {
560 let receipts =
561 Receipts { receipt_vec: vec![vec![ReceiptEnvelope::Eip1559(Default::default())]] };
562
563 let mut out = vec![];
564 receipts.encode(&mut out);
565
566 let mut out = out.as_slice();
567 let decoded = Receipts::<ReceiptEnvelope>::decode(&mut out).unwrap();
568
569 assert_eq!(receipts, decoded);
570 }
571}