reth_optimism_primitives/
receipt.rs1use alloc::vec::Vec;
2use alloy_consensus::{
3 Eip2718EncodableReceipt, Eip658Value, Receipt, ReceiptWithBloom, RlpDecodableReceipt,
4 RlpEncodableReceipt, TxReceipt, Typed2718,
5};
6use alloy_eips::{
7 eip2718::{Eip2718Result, IsTyped2718},
8 Decodable2718, Encodable2718,
9};
10use alloy_primitives::{Bloom, Log};
11use alloy_rlp::{BufMut, Decodable, Encodable, Header};
12use op_alloy_consensus::{OpDepositReceipt, OpReceipt, OpTxType};
13use reth_primitives_traits::InMemorySize;
14
15pub trait DepositReceipt: reth_primitives_traits::Receipt {
17 fn as_deposit_receipt_mut(&mut self) -> Option<&mut OpDepositReceipt>;
19
20 fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt>;
22}
23
24impl DepositReceipt for OpReceipt {
25 fn as_deposit_receipt_mut(&mut self) -> Option<&mut OpDepositReceipt> {
26 match self {
27 Self::Deposit(receipt) => Some(receipt),
28 _ => None,
29 }
30 }
31
32 fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt> {
33 match self {
34 Self::Deposit(receipt) => Some(receipt),
35 _ => None,
36 }
37 }
38}
39
40#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
41pub(super) mod serde_bincode_compat {
42 use serde::{Deserialize, Deserializer, Serialize, Serializer};
43 use serde_with::{DeserializeAs, SerializeAs};
44
45 #[allow(rustdoc::private_doc_tests)]
64 #[derive(Debug, Serialize, Deserialize)]
65 pub enum OpReceipt<'a> {
66 Legacy(alloy_consensus::serde_bincode_compat::Receipt<'a, alloy_primitives::Log>),
68 Eip2930(alloy_consensus::serde_bincode_compat::Receipt<'a, alloy_primitives::Log>),
70 Eip1559(alloy_consensus::serde_bincode_compat::Receipt<'a, alloy_primitives::Log>),
72 Eip7702(alloy_consensus::serde_bincode_compat::Receipt<'a, alloy_primitives::Log>),
74 Deposit(
76 op_alloy_consensus::serde_bincode_compat::OpDepositReceipt<'a, alloy_primitives::Log>,
77 ),
78 }
79
80 impl<'a> From<&'a super::OpReceipt> for OpReceipt<'a> {
81 fn from(value: &'a super::OpReceipt) -> Self {
82 match value {
83 super::OpReceipt::Legacy(receipt) => Self::Legacy(receipt.into()),
84 super::OpReceipt::Eip2930(receipt) => Self::Eip2930(receipt.into()),
85 super::OpReceipt::Eip1559(receipt) => Self::Eip1559(receipt.into()),
86 super::OpReceipt::Eip7702(receipt) => Self::Eip7702(receipt.into()),
87 super::OpReceipt::Deposit(receipt) => Self::Deposit(receipt.into()),
88 }
89 }
90 }
91
92 impl<'a> From<OpReceipt<'a>> for super::OpReceipt {
93 fn from(value: OpReceipt<'a>) -> Self {
94 match value {
95 OpReceipt::Legacy(receipt) => Self::Legacy(receipt.into()),
96 OpReceipt::Eip2930(receipt) => Self::Eip2930(receipt.into()),
97 OpReceipt::Eip1559(receipt) => Self::Eip1559(receipt.into()),
98 OpReceipt::Eip7702(receipt) => Self::Eip7702(receipt.into()),
99 OpReceipt::Deposit(receipt) => Self::Deposit(receipt.into()),
100 }
101 }
102 }
103
104 impl SerializeAs<super::OpReceipt> for OpReceipt<'_> {
105 fn serialize_as<S>(source: &super::OpReceipt, serializer: S) -> Result<S::Ok, S::Error>
106 where
107 S: Serializer,
108 {
109 OpReceipt::<'_>::from(source).serialize(serializer)
110 }
111 }
112
113 impl<'de> DeserializeAs<'de, super::OpReceipt> for OpReceipt<'de> {
114 fn deserialize_as<D>(deserializer: D) -> Result<super::OpReceipt, D::Error>
115 where
116 D: Deserializer<'de>,
117 {
118 OpReceipt::<'_>::deserialize(deserializer).map(Into::into)
119 }
120 }
121
122 #[cfg(test)]
123 mod tests {
124 use crate::{receipt::serde_bincode_compat, OpReceipt};
125 use arbitrary::Arbitrary;
126 use rand::Rng;
127 use serde::{Deserialize, Serialize};
128 use serde_with::serde_as;
129
130 #[test]
131 fn test_tx_bincode_roundtrip() {
132 #[serde_as]
133 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
134 struct Data {
135 #[serde_as(as = "serde_bincode_compat::OpReceipt<'_>")]
136 receipt: OpReceipt,
137 }
138
139 let mut bytes = [0u8; 1024];
140 rand::rng().fill(bytes.as_mut_slice());
141 let mut data = Data {
142 receipt: OpReceipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(),
143 };
144 let success = data.receipt.as_receipt_mut().status.coerce_status();
145 data.receipt.as_receipt_mut().status = success.into();
147
148 let encoded = bincode::serialize(&data).unwrap();
149 let decoded: Data = bincode::deserialize(&encoded).unwrap();
150 assert_eq!(decoded, data);
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use alloy_eips::eip2718::Encodable2718;
159 use alloy_primitives::{address, b256, bytes, hex_literal::hex, Bytes};
160 use alloy_rlp::Encodable;
161 use reth_codecs::Compact;
162
163 #[test]
164 fn test_decode_receipt() {
165 reth_codecs::test_utils::test_decode::<OpReceipt>(&hex!(
166 "c30328b52ffd23fc426961a00105007eb0042307705a97e503562eacf2b95060cce9de6de68386b6c155b73a9650021a49e2f8baad17f30faff5899d785c4c0873e45bc268bcf07560106424570d11f9a59e8f3db1efa4ceec680123712275f10d92c3411e1caaa11c7c5d591bc11487168e09934a9986848136da1b583babf3a7188e3aed007a1520f1cf4c1ca7d3482c6c28d37c298613c70a76940008816c4c95644579fd08471dc34732fd0f24"
167 ));
168 }
169
170 #[test]
172 fn encode_legacy_receipt() {
173 let expected = hex!(
174 "f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
175 );
176
177 let mut data = Vec::with_capacity(expected.length());
178 let receipt = ReceiptWithBloom {
179 receipt: OpReceipt::Legacy(Receipt::<Log> {
180 status: Eip658Value::Eip658(false),
181 cumulative_gas_used: 0x1,
182 logs: vec![Log::new_unchecked(
183 address!("0x0000000000000000000000000000000000000011"),
184 vec![
185 b256!("0x000000000000000000000000000000000000000000000000000000000000dead"),
186 b256!("0x000000000000000000000000000000000000000000000000000000000000beef"),
187 ],
188 bytes!("0100ff"),
189 )],
190 }),
191 logs_bloom: [0; 256].into(),
192 };
193
194 receipt.encode(&mut data);
195
196 assert_eq!(receipt.length(), expected.len());
198 assert_eq!(data, expected);
199 }
200
201 #[test]
203 fn decode_legacy_receipt() {
204 let data = hex!(
205 "f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
206 );
207
208 let expected = ReceiptWithBloom {
210 receipt: OpReceipt::Legacy(Receipt::<Log> {
211 status: Eip658Value::Eip658(false),
212 cumulative_gas_used: 0x1,
213 logs: vec![Log::new_unchecked(
214 address!("0x0000000000000000000000000000000000000011"),
215 vec![
216 b256!("0x000000000000000000000000000000000000000000000000000000000000dead"),
217 b256!("0x000000000000000000000000000000000000000000000000000000000000beef"),
218 ],
219 bytes!("0100ff"),
220 )],
221 }),
222 logs_bloom: [0; 256].into(),
223 };
224
225 let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
226 assert_eq!(receipt, expected);
227 }
228
229 #[test]
230 fn decode_deposit_receipt_regolith_roundtrip() {
231 let data = hex!(
232 "b901107ef9010c0182b741b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0833d3bbf"
233 );
234
235 let expected = ReceiptWithBloom {
237 receipt: OpReceipt::Deposit(OpDepositReceipt {
238 inner: Receipt::<Log> {
239 status: Eip658Value::Eip658(true),
240 cumulative_gas_used: 46913,
241 logs: vec![],
242 },
243 deposit_nonce: Some(4012991),
244 deposit_receipt_version: None,
245 }),
246 logs_bloom: [0; 256].into(),
247 };
248
249 let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
250 assert_eq!(receipt, expected);
251
252 let mut buf = Vec::with_capacity(data.len());
253 receipt.encode(&mut buf);
254 assert_eq!(buf, &data[..]);
255 }
256
257 #[test]
258 fn decode_deposit_receipt_canyon_roundtrip() {
259 let data = hex!(
260 "b901117ef9010d0182b741b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0833d3bbf01"
261 );
262
263 let expected = ReceiptWithBloom {
265 receipt: OpReceipt::Deposit(OpDepositReceipt {
266 inner: Receipt::<Log> {
267 status: Eip658Value::Eip658(true),
268 cumulative_gas_used: 46913,
269 logs: vec![],
270 },
271 deposit_nonce: Some(4012991),
272 deposit_receipt_version: Some(1),
273 }),
274 logs_bloom: [0; 256].into(),
275 };
276
277 let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
278 assert_eq!(receipt, expected);
279
280 let mut buf = Vec::with_capacity(data.len());
281 expected.encode(&mut buf);
282 assert_eq!(buf, &data[..]);
283 }
284
285 #[test]
286 fn gigantic_receipt() {
287 let receipt = OpReceipt::Legacy(Receipt::<Log> {
288 status: Eip658Value::Eip658(true),
289 cumulative_gas_used: 16747627,
290 logs: vec![
291 Log::new_unchecked(
292 address!("0x4bf56695415f725e43c3e04354b604bcfb6dfb6e"),
293 vec![b256!(
294 "0xc69dc3d7ebff79e41f525be431d5cd3cc08f80eaf0f7819054a726eeb7086eb9"
295 )],
296 Bytes::from(vec![1; 0xffffff]),
297 ),
298 Log::new_unchecked(
299 address!("0xfaca325c86bf9c2d5b413cd7b90b209be92229c2"),
300 vec![b256!(
301 "0x8cca58667b1e9ffa004720ac99a3d61a138181963b294d270d91c53d36402ae2"
302 )],
303 Bytes::from(vec![1; 0xffffff]),
304 ),
305 ],
306 });
307
308 let mut data = vec![];
309 receipt.to_compact(&mut data);
310 let (decoded, _) = OpReceipt::from_compact(&data[..], data.len());
311 assert_eq!(decoded, receipt);
312 }
313
314 #[test]
315 fn test_encode_2718_length() {
316 let receipt = ReceiptWithBloom {
317 receipt: OpReceipt::Eip1559(Receipt::<Log> {
318 status: Eip658Value::Eip658(true),
319 cumulative_gas_used: 21000,
320 logs: vec![],
321 }),
322 logs_bloom: Bloom::default(),
323 };
324
325 let encoded = receipt.encoded_2718();
326 assert_eq!(
327 encoded.len(),
328 receipt.encode_2718_len(),
329 "Encoded length should match the actual encoded data length"
330 );
331
332 let legacy_receipt = ReceiptWithBloom {
334 receipt: OpReceipt::Legacy(Receipt::<Log> {
335 status: Eip658Value::Eip658(true),
336 cumulative_gas_used: 21000,
337 logs: vec![],
338 }),
339 logs_bloom: Bloom::default(),
340 };
341
342 let legacy_encoded = legacy_receipt.encoded_2718();
343 assert_eq!(
344 legacy_encoded.len(),
345 legacy_receipt.encode_2718_len(),
346 "Encoded length for legacy receipt should match the actual encoded data length"
347 );
348 }
349}