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: TxTy> EthereumReceipt<T> {
71 pub fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize {
73 self.success.length()
74 + self.cumulative_gas_used.length()
75 + bloom.length()
76 + self.logs.length()
77 }
78
79 pub fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn BufMut) {
81 self.success.encode(out);
82 self.cumulative_gas_used.encode(out);
83 bloom.encode(out);
84 self.logs.encode(out);
85 }
86
87 pub fn rlp_header_inner(&self, bloom: &Bloom) -> Header {
89 Header { list: true, payload_length: self.rlp_encoded_fields_length(bloom) }
90 }
91
92 pub fn rlp_decode_inner(
94 buf: &mut &[u8],
95 tx_type: T,
96 ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
97 let header = Header::decode(buf)?;
98 if !header.list {
99 return Err(alloy_rlp::Error::UnexpectedString);
100 }
101
102 let remaining = buf.len();
103
104 let success = Decodable::decode(buf)?;
105 let cumulative_gas_used = Decodable::decode(buf)?;
106 let logs_bloom = Decodable::decode(buf)?;
107 let logs = Decodable::decode(buf)?;
108
109 let this = ReceiptWithBloom {
110 receipt: Self { tx_type, success, cumulative_gas_used, logs },
111 logs_bloom,
112 };
113
114 if buf.len() + header.payload_length != remaining {
115 return Err(alloy_rlp::Error::UnexpectedLength);
116 }
117
118 Ok(this)
119 }
120
121 pub fn calculate_receipt_root_no_memo(receipts: &[Self]) -> B256 {
125 ordered_trie_root_with_encoder(receipts, |r, buf| r.with_bloom_ref().encode_2718(buf))
126 }
127}
128
129impl<T: TxTy> Eip2718EncodableReceipt for EthereumReceipt<T> {
130 fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
131 !self.tx_type.is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload()
132 }
133
134 fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
135 if !self.tx_type.is_legacy() {
136 out.put_u8(self.tx_type.ty());
137 }
138 self.rlp_header_inner(bloom).encode(out);
139 self.rlp_encode_fields(bloom, out);
140 }
141}
142
143impl<T: TxTy> Eip2718DecodableReceipt for EthereumReceipt<T> {
144 fn typed_decode_with_bloom(ty: u8, buf: &mut &[u8]) -> Eip2718Result<ReceiptWithBloom<Self>> {
145 Ok(Self::rlp_decode_inner(buf, T::try_from(ty)?)?)
146 }
147
148 fn fallback_decode_with_bloom(buf: &mut &[u8]) -> Eip2718Result<ReceiptWithBloom<Self>> {
149 Ok(Self::rlp_decode_inner(buf, T::try_from(0)?)?)
150 }
151}
152
153impl<T: TxTy> RlpEncodableReceipt for EthereumReceipt<T> {
154 fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
155 let payload_length = self.eip2718_encoded_length_with_bloom(bloom);
156
157 if !self.tx_type.is_legacy() {
158 payload_length + Header { list: false, payload_length }.length()
159 } else {
160 payload_length
161 }
162 }
163
164 fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
165 if !self.tx_type.is_legacy() {
166 Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom) }
167 .encode(out);
168 }
169 self.eip2718_encode_with_bloom(bloom, out);
170 }
171}
172
173impl<T: TxTy> RlpDecodableReceipt for EthereumReceipt<T> {
174 fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
175 let header_buf = &mut &**buf;
176 let header = Header::decode(header_buf)?;
177
178 if header.list {
180 return Self::rlp_decode_inner(buf, T::try_from(0)?);
181 }
182
183 *buf = *header_buf;
185
186 let remaining = buf.len();
187
188 let tx_type = T::decode(buf)?;
189 let this = Self::rlp_decode_inner(buf, tx_type)?;
190
191 if buf.len() + header.payload_length != remaining {
192 return Err(alloy_rlp::Error::UnexpectedLength);
193 }
194
195 Ok(this)
196 }
197}
198
199impl<T, L> TxReceipt for EthereumReceipt<T, L>
200where
201 T: TxTy,
202 L: Send + Sync + Clone + Debug + Eq + AsRef<Log>,
203{
204 type Log = L;
205
206 fn status_or_post_state(&self) -> Eip658Value {
207 self.success.into()
208 }
209
210 fn status(&self) -> bool {
211 self.success
212 }
213
214 fn bloom(&self) -> Bloom {
215 alloy_primitives::logs_bloom(self.logs.iter().map(|l| l.as_ref()))
216 }
217
218 fn cumulative_gas_used(&self) -> u64 {
219 self.cumulative_gas_used
220 }
221
222 fn logs(&self) -> &[L] {
223 &self.logs
224 }
225
226 fn into_logs(self) -> Vec<L> {
227 self.logs
228 }
229}
230
231impl<T: TxTy> Typed2718 for EthereumReceipt<T> {
232 fn ty(&self) -> u8 {
233 self.tx_type.ty()
234 }
235}
236
237impl<T: TxTy + IsTyped2718> IsTyped2718 for EthereumReceipt<T> {
238 fn is_type(type_id: u8) -> bool {
239 <T as IsTyped2718>::is_type(type_id)
240 }
241}
242
243impl<T: TxTy> InMemorySize for EthereumReceipt<T> {
244 fn size(&self) -> usize {
245 core::mem::size_of::<Self>() + self.logs.iter().map(|log| log.size()).sum::<usize>()
246 }
247}
248
249impl<T> From<ReceiptEnvelope<T>> for EthereumReceipt<TxType>
250where
251 T: Into<Log>,
252{
253 fn from(value: ReceiptEnvelope<T>) -> Self {
254 let value = value.into_primitives_receipt();
255 Self {
256 tx_type: value.tx_type(),
257 success: value.is_success(),
258 cumulative_gas_used: value.cumulative_gas_used(),
259 logs: value.into_logs(),
260 }
261 }
262}
263
264impl<T, L> From<EthereumReceipt<T, L>> for crate::Receipt<L> {
265 fn from(value: EthereumReceipt<T, L>) -> Self {
266 Self {
267 status: value.success.into(),
268 cumulative_gas_used: value.cumulative_gas_used,
269 logs: value.logs,
270 }
271 }
272}
273
274impl<L> From<EthereumReceipt<TxType, L>> for ReceiptEnvelope<L>
275where
276 L: Send + Sync + Clone + Debug + Eq + AsRef<Log>,
277{
278 fn from(value: EthereumReceipt<TxType, L>) -> Self {
279 let tx_type = value.tx_type;
280 let receipt = value.into_with_bloom().map_receipt(Into::into);
281 match tx_type {
282 TxType::Legacy => Self::Legacy(receipt),
283 TxType::Eip2930 => Self::Eip2930(receipt),
284 TxType::Eip1559 => Self::Eip1559(receipt),
285 TxType::Eip4844 => Self::Eip4844(receipt),
286 TxType::Eip7702 => Self::Eip7702(receipt),
287 }
288 }
289}
290
291#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
292pub(crate) mod serde_bincode_compat {
293 use alloc::{borrow::Cow, vec::Vec};
294 use alloy_eips::eip2718::Eip2718Error;
295 use alloy_primitives::{Log, U8};
296 use core::fmt::Debug;
297 use serde::{Deserialize, Deserializer, Serialize, Serializer};
298 use serde_with::{DeserializeAs, SerializeAs};
299
300 #[derive(Debug, Serialize, Deserialize)]
316 #[serde(bound(deserialize = "T: TryFrom<u8, Error = Eip2718Error>"))]
317 pub struct EthereumReceipt<'a, T = crate::TxType> {
318 #[serde(deserialize_with = "deserde_txtype")]
320 pub tx_type: T,
321 pub success: bool,
325 pub cumulative_gas_used: u64,
327 pub logs: Cow<'a, Vec<Log>>,
329 }
330
331 fn deserde_txtype<'de, D, T>(deserializer: D) -> Result<T, D::Error>
333 where
334 D: Deserializer<'de>,
335 T: TryFrom<u8, Error = Eip2718Error>,
336 {
337 U8::deserialize(deserializer)?.to::<u8>().try_into().map_err(serde::de::Error::custom)
338 }
339
340 impl<'a, T: Copy> From<&'a super::EthereumReceipt<T>> for EthereumReceipt<'a, T> {
341 fn from(value: &'a super::EthereumReceipt<T>) -> Self {
342 Self {
343 tx_type: value.tx_type,
344 success: value.success,
345 cumulative_gas_used: value.cumulative_gas_used,
346 logs: Cow::Borrowed(&value.logs),
347 }
348 }
349 }
350
351 impl<'a, T> From<EthereumReceipt<'a, T>> for super::EthereumReceipt<T> {
352 fn from(value: EthereumReceipt<'a, T>) -> Self {
353 Self {
354 tx_type: value.tx_type,
355 success: value.success,
356 cumulative_gas_used: value.cumulative_gas_used,
357 logs: value.logs.into_owned(),
358 }
359 }
360 }
361
362 impl<T: Copy + Serialize> SerializeAs<super::EthereumReceipt<T>> for EthereumReceipt<'_, T> {
363 fn serialize_as<S>(
364 source: &super::EthereumReceipt<T>,
365 serializer: S,
366 ) -> Result<S::Ok, S::Error>
367 where
368 S: Serializer,
369 {
370 EthereumReceipt::<'_>::from(source).serialize(serializer)
371 }
372 }
373
374 impl<'de, T: TryFrom<u8, Error = Eip2718Error>> DeserializeAs<'de, super::EthereumReceipt<T>>
375 for EthereumReceipt<'de, T>
376 {
377 fn deserialize_as<D>(deserializer: D) -> Result<super::EthereumReceipt<T>, D::Error>
378 where
379 D: Deserializer<'de>,
380 {
381 EthereumReceipt::<'_, T>::deserialize(deserializer).map(Into::into)
382 }
383 }
384
385 #[cfg(test)]
386 mod tests {
387 use crate::TxType;
388 use arbitrary::Arbitrary;
389 use bincode::config;
390 use rand::Rng;
391 use serde_with::serde_as;
392
393 use super::super::EthereumReceipt;
394
395 #[test]
396 fn test_ethereum_receipt_bincode_roundtrip() {
397 #[serde_as]
398 #[derive(Debug, PartialEq, Eq)]
399 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
400 struct Data {
401 #[serde_as(as = "super::EthereumReceipt<'_, TxType>")]
402 receipt: EthereumReceipt<TxType>,
403 }
404
405 let mut bytes = [0u8; 1024];
406 rand::thread_rng().fill(bytes.as_mut_slice());
407 let data = Data {
408 receipt: EthereumReceipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
409 .unwrap(),
410 };
411
412 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
413 let (decoded, _): (Data, _) =
414 bincode::serde::decode_from_slice(&encoded, config::legacy()).unwrap();
415 assert_eq!(decoded, data);
416 }
417 }
418}