1use crate::{
2 proofs::ordered_trie_root_with_encoder,
3 receipt::{
4 Eip2718DecodableReceipt, Eip2718EncodableReceipt, Eip658Value, RlpDecodableReceipt,
5 RlpEncodableReceipt, TxReceipt,
6 },
7 InMemorySize, ReceiptEnvelope, ReceiptWithBloom, TxType,
8};
9use alloc::vec::Vec;
10use alloy_eips::{
11 eip2718::{Eip2718Error, Eip2718Result, Encodable2718, IsTyped2718},
12 Typed2718,
13};
14use alloy_primitives::{Bloom, Log, B256};
15use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpDecodable, RlpEncodable};
16use core::fmt::Debug;
17
18pub trait TxTy:
21 Debug
22 + Copy
23 + Eq
24 + Send
25 + Sync
26 + InMemorySize
27 + Typed2718
28 + TryFrom<u8, Error = Eip2718Error>
29 + Decodable
30 + 'static
31{
32}
33
34impl<T> TxTy for T where
35 T: Debug
36 + Copy
37 + Eq
38 + Send
39 + Sync
40 + InMemorySize
41 + Typed2718
42 + TryFrom<u8, Error = Eip2718Error>
43 + Decodable
44 + 'static
45{
46}
47
48#[derive(Clone, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
53#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
54pub struct EthereumReceipt<T = TxType, L = Log> {
55 #[cfg_attr(feature = "serde", serde(rename = "type"))]
57 pub tx_type: T,
58 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity", rename = "status"))]
62 pub success: bool,
63 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
65 pub cumulative_gas_used: u64,
66 pub logs: Vec<L>,
68}
69
70impl<T, L> EthereumReceipt<T, L> {
71 pub fn map_logs<U>(self, f: impl FnMut(L) -> U) -> EthereumReceipt<T, U> {
75 let Self { tx_type, success, cumulative_gas_used, logs } = self;
76 EthereumReceipt {
77 tx_type,
78 success,
79 cumulative_gas_used,
80 logs: logs.into_iter().map(f).collect(),
81 }
82 }
83}
84
85impl<T: TxTy> EthereumReceipt<T> {
86 pub fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize {
88 self.success.length()
89 + self.cumulative_gas_used.length()
90 + bloom.length()
91 + self.logs.length()
92 }
93
94 pub fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn BufMut) {
96 self.success.encode(out);
97 self.cumulative_gas_used.encode(out);
98 bloom.encode(out);
99 self.logs.encode(out);
100 }
101
102 pub fn rlp_header_inner(&self, bloom: &Bloom) -> Header {
104 Header { list: true, payload_length: self.rlp_encoded_fields_length(bloom) }
105 }
106
107 pub fn rlp_decode_inner(
109 buf: &mut &[u8],
110 tx_type: T,
111 ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
112 let header = Header::decode(buf)?;
113 if !header.list {
114 return Err(alloy_rlp::Error::UnexpectedString);
115 }
116
117 let remaining = buf.len();
118
119 let success = Decodable::decode(buf)?;
120 let cumulative_gas_used = Decodable::decode(buf)?;
121 let logs_bloom = Decodable::decode(buf)?;
122 let logs = Decodable::decode(buf)?;
123
124 let this = ReceiptWithBloom {
125 receipt: Self { tx_type, success, cumulative_gas_used, logs },
126 logs_bloom,
127 };
128
129 if buf.len() + header.payload_length != remaining {
130 return Err(alloy_rlp::Error::UnexpectedLength);
131 }
132
133 Ok(this)
134 }
135
136 pub fn calculate_receipt_root_no_memo(receipts: &[Self]) -> B256 {
140 ordered_trie_root_with_encoder(receipts, |r, buf| r.with_bloom_ref().encode_2718(buf))
141 }
142}
143
144impl<T: TxTy> Eip2718EncodableReceipt for EthereumReceipt<T> {
145 fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
146 !self.tx_type.is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload()
147 }
148
149 fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
150 if !self.tx_type.is_legacy() {
151 out.put_u8(self.tx_type.ty());
152 }
153 self.rlp_header_inner(bloom).encode(out);
154 self.rlp_encode_fields(bloom, out);
155 }
156}
157
158impl<T: TxTy> Eip2718DecodableReceipt for EthereumReceipt<T> {
159 fn typed_decode_with_bloom(ty: u8, buf: &mut &[u8]) -> Eip2718Result<ReceiptWithBloom<Self>> {
160 Ok(Self::rlp_decode_inner(buf, T::try_from(ty)?)?)
161 }
162
163 fn fallback_decode_with_bloom(buf: &mut &[u8]) -> Eip2718Result<ReceiptWithBloom<Self>> {
164 Ok(Self::rlp_decode_inner(buf, T::try_from(0)?)?)
165 }
166}
167
168impl<T: TxTy> RlpEncodableReceipt for EthereumReceipt<T> {
169 fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
170 let payload_length = self.eip2718_encoded_length_with_bloom(bloom);
171
172 if !self.tx_type.is_legacy() {
173 payload_length + Header { list: false, payload_length }.length()
174 } else {
175 payload_length
176 }
177 }
178
179 fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
180 if !self.tx_type.is_legacy() {
181 Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom) }
182 .encode(out);
183 }
184 self.eip2718_encode_with_bloom(bloom, out);
185 }
186}
187
188impl<T: TxTy> RlpDecodableReceipt for EthereumReceipt<T> {
189 fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
190 let header_buf = &mut &**buf;
191 let header = Header::decode(header_buf)?;
192
193 if header.list {
195 return Self::rlp_decode_inner(buf, T::try_from(0)?);
196 }
197
198 *buf = *header_buf;
200
201 let remaining = buf.len();
202
203 let tx_type = T::decode(buf)?;
204 let this = Self::rlp_decode_inner(buf, tx_type)?;
205
206 if buf.len() + header.payload_length != remaining {
207 return Err(alloy_rlp::Error::UnexpectedLength);
208 }
209
210 Ok(this)
211 }
212}
213
214impl<T, L> TxReceipt for EthereumReceipt<T, L>
215where
216 T: TxTy,
217 L: Send + Sync + Clone + Debug + Eq + AsRef<Log>,
218{
219 type Log = L;
220
221 fn status_or_post_state(&self) -> Eip658Value {
222 self.success.into()
223 }
224
225 fn status(&self) -> bool {
226 self.success
227 }
228
229 fn bloom(&self) -> Bloom {
230 alloy_primitives::logs_bloom(self.logs.iter().map(|l| l.as_ref()))
231 }
232
233 fn cumulative_gas_used(&self) -> u64 {
234 self.cumulative_gas_used
235 }
236
237 fn logs(&self) -> &[L] {
238 &self.logs
239 }
240
241 fn into_logs(self) -> Vec<L> {
242 self.logs
243 }
244}
245
246impl<T: TxTy> Typed2718 for EthereumReceipt<T> {
247 fn ty(&self) -> u8 {
248 self.tx_type.ty()
249 }
250}
251
252impl<T: TxTy + IsTyped2718> IsTyped2718 for EthereumReceipt<T> {
253 fn is_type(type_id: u8) -> bool {
254 <T as IsTyped2718>::is_type(type_id)
255 }
256}
257
258impl<T: TxTy> InMemorySize for EthereumReceipt<T> {
259 fn size(&self) -> usize {
260 core::mem::size_of::<Self>() + self.logs.iter().map(|log| log.size()).sum::<usize>()
261 }
262}
263
264impl<T> From<ReceiptEnvelope<T>> for EthereumReceipt<TxType>
265where
266 T: Into<Log>,
267{
268 fn from(value: ReceiptEnvelope<T>) -> Self {
269 let value = value.into_primitives_receipt();
270 Self {
271 tx_type: value.tx_type(),
272 success: value.is_success(),
273 cumulative_gas_used: value.cumulative_gas_used(),
274 logs: value.into_logs(),
275 }
276 }
277}
278
279impl<T, L> From<EthereumReceipt<T, L>> for crate::Receipt<L> {
280 fn from(value: EthereumReceipt<T, L>) -> Self {
281 Self {
282 status: value.success.into(),
283 cumulative_gas_used: value.cumulative_gas_used,
284 logs: value.logs,
285 }
286 }
287}
288
289impl<L> From<EthereumReceipt<TxType, L>> for ReceiptEnvelope<L>
290where
291 L: Send + Sync + Clone + Debug + Eq + AsRef<Log>,
292{
293 fn from(value: EthereumReceipt<TxType, L>) -> Self {
294 let tx_type = value.tx_type;
295 let receipt = value.into_with_bloom().map_receipt(Into::into);
296 match tx_type {
297 TxType::Legacy => Self::Legacy(receipt),
298 TxType::Eip2930 => Self::Eip2930(receipt),
299 TxType::Eip1559 => Self::Eip1559(receipt),
300 TxType::Eip4844 => Self::Eip4844(receipt),
301 TxType::Eip7702 => Self::Eip7702(receipt),
302 }
303 }
304}
305
306#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
307pub(crate) mod serde_bincode_compat {
308 use alloc::{borrow::Cow, vec::Vec};
309 use alloy_eips::eip2718::Eip2718Error;
310 use alloy_primitives::{Log, U8};
311 use core::fmt::Debug;
312 use serde::{Deserialize, Deserializer, Serialize, Serializer};
313 use serde_with::{DeserializeAs, SerializeAs};
314
315 #[derive(Debug, Serialize, Deserialize)]
331 #[serde(bound(deserialize = "T: TryFrom<u8, Error = Eip2718Error>"))]
332 pub struct EthereumReceipt<'a, T = crate::TxType> {
333 #[serde(deserialize_with = "deserde_txtype")]
335 pub tx_type: T,
336 pub success: bool,
340 pub cumulative_gas_used: u64,
342 pub logs: Cow<'a, Vec<Log>>,
344 }
345
346 fn deserde_txtype<'de, D, T>(deserializer: D) -> Result<T, D::Error>
348 where
349 D: Deserializer<'de>,
350 T: TryFrom<u8, Error = Eip2718Error>,
351 {
352 U8::deserialize(deserializer)?.to::<u8>().try_into().map_err(serde::de::Error::custom)
353 }
354
355 impl<'a, T: Copy> From<&'a super::EthereumReceipt<T>> for EthereumReceipt<'a, T> {
356 fn from(value: &'a super::EthereumReceipt<T>) -> Self {
357 Self {
358 tx_type: value.tx_type,
359 success: value.success,
360 cumulative_gas_used: value.cumulative_gas_used,
361 logs: Cow::Borrowed(&value.logs),
362 }
363 }
364 }
365
366 impl<'a, T> From<EthereumReceipt<'a, T>> for super::EthereumReceipt<T> {
367 fn from(value: EthereumReceipt<'a, T>) -> Self {
368 Self {
369 tx_type: value.tx_type,
370 success: value.success,
371 cumulative_gas_used: value.cumulative_gas_used,
372 logs: value.logs.into_owned(),
373 }
374 }
375 }
376
377 impl<T: Copy + Serialize> SerializeAs<super::EthereumReceipt<T>> for EthereumReceipt<'_, T> {
378 fn serialize_as<S>(
379 source: &super::EthereumReceipt<T>,
380 serializer: S,
381 ) -> Result<S::Ok, S::Error>
382 where
383 S: Serializer,
384 {
385 EthereumReceipt::<'_>::from(source).serialize(serializer)
386 }
387 }
388
389 impl<'de, T: TryFrom<u8, Error = Eip2718Error>> DeserializeAs<'de, super::EthereumReceipt<T>>
390 for EthereumReceipt<'de, T>
391 {
392 fn deserialize_as<D>(deserializer: D) -> Result<super::EthereumReceipt<T>, D::Error>
393 where
394 D: Deserializer<'de>,
395 {
396 EthereumReceipt::<'_, T>::deserialize(deserializer).map(Into::into)
397 }
398 }
399
400 #[cfg(test)]
401 mod tests {
402 use crate::TxType;
403 use arbitrary::Arbitrary;
404 use bincode::config;
405 use rand::Rng;
406 use serde_with::serde_as;
407
408 use super::super::EthereumReceipt;
409
410 #[test]
411 fn test_ethereum_receipt_bincode_roundtrip() {
412 #[serde_as]
413 #[derive(Debug, PartialEq, Eq)]
414 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
415 struct Data {
416 #[serde_as(as = "super::EthereumReceipt<'_, TxType>")]
417 receipt: EthereumReceipt<TxType>,
418 }
419
420 let mut bytes = [0u8; 1024];
421 rand::thread_rng().fill(bytes.as_mut_slice());
422 let data = Data {
423 receipt: EthereumReceipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
424 .unwrap(),
425 };
426
427 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
428 let (decoded, _): (Data, _) =
429 bincode::serde::decode_from_slice(&encoded, config::legacy()).unwrap();
430 assert_eq!(decoded, data);
431 }
432 }
433}