alloy_consensus_any/receipt/
envelope.rs1use alloc::vec::Vec;
2use alloy_consensus::{Eip658Value, Receipt, ReceiptWithBloom, TxReceipt};
3use alloy_eips::{
4 eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
5 Typed2718,
6};
7use alloy_primitives::{bytes::BufMut, Bloom, Log};
8use alloy_rlp::{Decodable, Encodable};
9use core::fmt;
10
11#[derive(Clone, Debug, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21#[doc(alias = "AnyTransactionReceiptEnvelope", alias = "AnyTxReceiptEnvelope")]
22pub struct AnyReceiptEnvelope<T = Log> {
23 #[cfg_attr(feature = "serde", serde(flatten))]
25 pub inner: ReceiptWithBloom<Receipt<T>>,
26 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
28 pub r#type: u8,
29}
30
31impl<T> AnyReceiptEnvelope<T> {
32 pub const fn is_legacy(&self) -> bool {
34 self.r#type == 0
35 }
36}
37
38impl<T: Encodable> AnyReceiptEnvelope<T> {
39 pub fn rlp_payload_length(&self) -> usize {
41 let length = self.inner.length();
42 if self.is_legacy() {
43 length
44 } else {
45 length + 1
46 }
47 }
48}
49
50impl<T> AnyReceiptEnvelope<T> {
51 pub const fn is_success(&self) -> bool {
60 self.status()
61 }
62
63 pub const fn status(&self) -> bool {
72 self.inner.receipt.status.coerce_status()
73 }
74
75 pub const fn bloom(&self) -> Bloom {
77 self.inner.logs_bloom
78 }
79
80 pub const fn bloom_ref(&self) -> &Bloom {
82 &self.inner.logs_bloom
83 }
84
85 pub const fn cumulative_gas_used(&self) -> u64 {
87 self.inner.receipt.cumulative_gas_used
88 }
89
90 pub fn logs(&self) -> &[T] {
92 &self.inner.receipt.logs
93 }
94}
95
96impl<T> TxReceipt for AnyReceiptEnvelope<T>
97where
98 T: Clone + fmt::Debug + PartialEq + Eq + Send + Sync,
99{
100 type Log = T;
101
102 fn status_or_post_state(&self) -> Eip658Value {
103 self.inner.receipt.status
104 }
105
106 fn status(&self) -> bool {
107 self.status()
108 }
109
110 fn bloom(&self) -> Bloom {
111 self.bloom()
112 }
113
114 fn cumulative_gas_used(&self) -> u64 {
115 self.cumulative_gas_used()
116 }
117
118 fn logs(&self) -> &[T] {
119 Self::logs(self)
120 }
121
122 fn into_logs(self) -> Vec<Self::Log> {
123 self.inner.receipt.logs
124 }
125}
126
127impl Typed2718 for AnyReceiptEnvelope {
128 fn ty(&self) -> u8 {
129 self.r#type
130 }
131}
132
133impl Encodable2718 for AnyReceiptEnvelope {
134 fn encode_2718_len(&self) -> usize {
135 self.inner.length() + !self.is_legacy() as usize
136 }
137
138 fn encode_2718(&self, out: &mut dyn BufMut) {
139 match self.type_flag() {
140 None => {}
141 Some(ty) => out.put_u8(ty),
142 }
143 self.inner.encode(out);
144 }
145}
146
147impl Decodable2718 for AnyReceiptEnvelope {
148 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
149 if ty == 0 {
153 return Err(Eip2718Error::UnexpectedType(ty));
154 }
155 let receipt = Decodable::decode(buf)?;
156 Ok(Self { inner: receipt, r#type: ty })
157 }
158
159 fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
160 let receipt = Decodable::decode(buf)?;
161 Ok(Self { inner: receipt, r#type: 0 })
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use alloc::vec;
169 use alloy_rlp::Header;
170
171 fn envelope(ty: u8) -> AnyReceiptEnvelope {
172 AnyReceiptEnvelope { inner: Default::default(), r#type: ty }
173 }
174
175 fn network_framed(payload: &[u8]) -> Vec<u8> {
178 let mut out = Vec::new();
179 Header { list: false, payload_length: payload.len() }.encode(&mut out);
180 out.extend_from_slice(payload);
181 out
182 }
183
184 #[test]
185 fn legacy_receipt_roundtrips_untagged() {
186 let envelope = envelope(0);
187 let encoded = envelope.encoded_2718();
188 assert!(encoded[0] >= 0xc0, "sanity: legacy receipts are encoded as a bare RLP list");
189 let mut network = Vec::new();
191 envelope.network_encode(&mut network);
192 assert_eq!(network, encoded);
193
194 assert_eq!(AnyReceiptEnvelope::decode_2718_exact(&encoded).unwrap(), envelope);
195 assert_eq!(AnyReceiptEnvelope::network_decode(&mut encoded.as_slice()).unwrap(), envelope);
196 }
197
198 #[test]
199 fn typed_receipt_roundtrips() {
200 let envelope = envelope(0x7e);
202 let encoded = envelope.encoded_2718();
203 assert_eq!(encoded[0], 0x7e, "sanity: typed receipts are prefixed with their type byte");
204 let mut network = Vec::new();
205 envelope.network_encode(&mut network);
206 assert_eq!(network, network_framed(&encoded));
207
208 assert_eq!(AnyReceiptEnvelope::decode_2718_exact(&encoded).unwrap(), envelope);
209 assert_eq!(AnyReceiptEnvelope::network_decode(&mut network.as_slice()).unwrap(), envelope);
210 }
211
212 #[test]
213 fn tagged_legacy_receipt_is_rejected() {
214 let mut tagged = vec![0x00];
218 tagged.extend_from_slice(&envelope(0).encoded_2718());
219 let tagged_network = network_framed(&tagged);
220
221 assert!(matches!(
222 AnyReceiptEnvelope::decode_2718_exact(&tagged),
223 Err(Eip2718Error::UnexpectedType(0))
224 ));
225 assert!(matches!(
226 AnyReceiptEnvelope::network_decode(&mut tagged_network.as_slice()),
227 Err(Eip2718Error::UnexpectedType(0))
228 ));
229 }
230}