1use crate::{
4 eip4844::{Blob, Bytes48},
5 eip7594::{Cell, CELLS_PER_EXT_BLOB},
6};
7use alloc::{boxed::Box, vec::Vec};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct BlobAndProofV1 {
13 pub blob: Box<Blob>,
15 pub proof: Bytes48,
17}
18
19#[cfg(feature = "ssz")]
20impl ssz::Encode for BlobAndProofV1 {
21 fn is_ssz_fixed_len() -> bool {
22 true
23 }
24
25 fn ssz_fixed_len() -> usize {
26 <Blob as ssz::Encode>::ssz_fixed_len() + <Bytes48 as ssz::Encode>::ssz_fixed_len()
27 }
28
29 fn ssz_bytes_len(&self) -> usize {
30 Self::ssz_fixed_len()
31 }
32
33 fn ssz_append(&self, buf: &mut Vec<u8>) {
34 ssz::Encode::ssz_append(self.blob.as_ref(), buf);
35 ssz::Encode::ssz_append(&self.proof, buf);
36 }
37}
38
39#[cfg(feature = "ssz")]
40impl ssz::Decode for BlobAndProofV1 {
41 fn is_ssz_fixed_len() -> bool {
42 true
43 }
44
45 fn ssz_fixed_len() -> usize {
46 <Blob as ssz::Decode>::ssz_fixed_len() + <Bytes48 as ssz::Decode>::ssz_fixed_len()
47 }
48
49 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
50 let mut builder = ssz::SszDecoderBuilder::new(bytes);
51 builder.register_type::<Blob>()?;
52 builder.register_type::<Bytes48>()?;
53
54 let mut decoder = builder.build()?;
55 Ok(Self { blob: Box::new(decoder.decode_next()?), proof: decoder.decode_next()? })
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
63pub struct BlobAndProofV2 {
64 pub blob: Box<Blob>,
66 pub proofs: Vec<Bytes48>,
68}
69
70#[cfg(feature = "ssz")]
71impl ssz::Encode for BlobAndProofV2 {
72 fn is_ssz_fixed_len() -> bool {
73 false
74 }
75
76 fn ssz_bytes_len(&self) -> usize {
77 <Blob as ssz::Encode>::ssz_fixed_len()
78 + <Vec<Bytes48> as ssz::Encode>::ssz_fixed_len()
79 + ssz::Encode::ssz_bytes_len(&self.proofs)
80 }
81
82 fn ssz_append(&self, buf: &mut Vec<u8>) {
83 let offset =
84 <Blob as ssz::Encode>::ssz_fixed_len() + <Vec<Bytes48> as ssz::Encode>::ssz_fixed_len();
85 let mut encoder = ssz::SszEncoder::container(buf, offset);
86 encoder.append(self.blob.as_ref());
87 encoder.append(&self.proofs);
88 encoder.finalize();
89 }
90}
91
92#[cfg(feature = "ssz")]
93impl ssz::Decode for BlobAndProofV2 {
94 fn is_ssz_fixed_len() -> bool {
95 false
96 }
97
98 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
99 let mut builder = ssz::SszDecoderBuilder::new(bytes);
100 builder.register_type::<Blob>()?;
101 builder.register_type::<Vec<Bytes48>>()?;
102
103 let mut decoder = builder.build()?;
104 let blob = decoder.decode_next()?;
105 let proofs: Vec<Bytes48> = decoder.decode_next()?;
106 if proofs.len() > crate::eip7594::CELLS_PER_EXT_BLOB {
107 return Err(ssz::DecodeError::BytesInvalid(format!(
108 "Invalid BlobAndProofV2: expected at most {} proofs, got {}",
109 crate::eip7594::CELLS_PER_EXT_BLOB,
110 proofs.len()
111 )));
112 }
113
114 Ok(Self { blob: Box::new(blob), proofs })
115 }
116}
117
118#[derive(Debug, Clone, Default, PartialEq, Eq)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
122pub struct BlobCellsAndProofsV1 {
123 pub blob_cells: Vec<Option<Cell>>,
125 pub proofs: Vec<Option<Bytes48>>,
127}
128
129#[cfg(feature = "ssz")]
130impl ssz::Encode for BlobCellsAndProofsV1 {
131 fn is_ssz_fixed_len() -> bool {
132 false
133 }
134
135 fn ssz_bytes_len(&self) -> usize {
136 <Vec<Option<Cell>> as ssz::Encode>::ssz_fixed_len()
137 + <Vec<Option<Bytes48>> as ssz::Encode>::ssz_fixed_len()
138 + ssz::Encode::ssz_bytes_len(&self.blob_cells)
139 + ssz::Encode::ssz_bytes_len(&self.proofs)
140 }
141
142 fn ssz_append(&self, buf: &mut Vec<u8>) {
143 let offset = <Vec<Option<Cell>> as ssz::Encode>::ssz_fixed_len()
144 + <Vec<Option<Bytes48>> as ssz::Encode>::ssz_fixed_len();
145 let mut encoder = ssz::SszEncoder::container(buf, offset);
146 encoder.append(&self.blob_cells);
147 encoder.append(&self.proofs);
148 encoder.finalize();
149 }
150}
151
152#[cfg(feature = "ssz")]
153impl ssz::Decode for BlobCellsAndProofsV1 {
154 fn is_ssz_fixed_len() -> bool {
155 false
156 }
157
158 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
159 let mut builder = ssz::SszDecoderBuilder::new(bytes);
160 builder.register_type::<Vec<Option<Cell>>>()?;
161 builder.register_type::<Vec<Option<Bytes48>>>()?;
162
163 let mut decoder = builder.build()?;
164 let blob_cells: Vec<Option<Cell>> = decoder.decode_next()?;
165 let proofs: Vec<Option<Bytes48>> = decoder.decode_next()?;
166
167 if blob_cells.len() > CELLS_PER_EXT_BLOB {
168 return Err(ssz::DecodeError::BytesInvalid(format!(
169 "Invalid BlobCellsAndProofsV1: expected at most {} blob cells, got {}",
170 CELLS_PER_EXT_BLOB,
171 blob_cells.len()
172 )));
173 }
174
175 if blob_cells.len() != proofs.len() {
176 return Err(ssz::DecodeError::BytesInvalid(format!(
177 "Invalid BlobCellsAndProofsV1: blob_cells length {} does not match proofs length {}",
178 blob_cells.len(),
179 proofs.len()
180 )));
181 }
182
183 if blob_cells.iter().zip(&proofs).any(|(cell, proof)| cell.is_some() != proof.is_some()) {
184 return Err(ssz::DecodeError::BytesInvalid(
185 "Invalid BlobCellsAndProofsV1: blob_cells and proofs must have matching null positions".into(),
186 ));
187 }
188
189 Ok(Self { blob_cells, proofs })
190 }
191}
192
193#[cfg(all(test, feature = "ssz"))]
194mod tests {
195 use super::*;
196 use crate::eip4844::BYTES_PER_BLOB;
197
198 #[test]
199 fn ssz_blob_and_proof_v1_roundtrip() {
200 let blob_and_proof = BlobAndProofV1 {
201 blob: Box::new(Blob::repeat_byte(0x42)),
202 proof: Bytes48::repeat_byte(0x24),
203 };
204
205 let encoded = ssz::Encode::as_ssz_bytes(&blob_and_proof);
206 assert_eq!(encoded.len(), BYTES_PER_BLOB + 48);
207 assert_eq!(&encoded[..BYTES_PER_BLOB], blob_and_proof.blob.as_slice());
208 assert_eq!(&encoded[BYTES_PER_BLOB..], blob_and_proof.proof.as_slice());
209
210 let decoded = <BlobAndProofV1 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap();
211 assert_eq!(decoded, blob_and_proof);
212 }
213
214 #[test]
215 fn ssz_blob_and_proof_v2_roundtrip() {
216 let proofs =
217 (0..CELLS_PER_EXT_BLOB).map(|i| Bytes48::repeat_byte(i as u8)).collect::<Vec<_>>();
218 let blob_and_proof = BlobAndProofV2 { blob: Box::new(Blob::repeat_byte(0x42)), proofs };
219
220 let encoded = ssz::Encode::as_ssz_bytes(&blob_and_proof);
221 let expected_offset = BYTES_PER_BLOB + ssz::BYTES_PER_LENGTH_OFFSET;
222 assert_eq!(encoded.len(), expected_offset + CELLS_PER_EXT_BLOB * 48);
223 assert_eq!(
224 u32::from_le_bytes(encoded[BYTES_PER_BLOB..expected_offset].try_into().unwrap())
225 as usize,
226 expected_offset
227 );
228 assert_eq!(&encoded[..BYTES_PER_BLOB], blob_and_proof.blob.as_slice());
229
230 let mut proof_chunks = encoded[expected_offset..].chunks_exact(48);
231 for (proof, chunk) in blob_and_proof.proofs.iter().zip(&mut proof_chunks) {
232 assert_eq!(chunk, proof.as_slice());
233 }
234 assert!(proof_chunks.remainder().is_empty());
235
236 let decoded = <BlobAndProofV2 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap();
237 assert_eq!(decoded, blob_and_proof);
238 }
239
240 #[test]
241 fn ssz_blob_and_proof_v2_rejects_too_many_proofs() {
242 let blob_and_proof = BlobAndProofV2 {
243 blob: Box::new(Blob::repeat_byte(0x42)),
244 proofs: vec![Bytes48::ZERO; CELLS_PER_EXT_BLOB + 1],
245 };
246 let encoded = ssz::Encode::as_ssz_bytes(&blob_and_proof);
247
248 let err = <BlobAndProofV2 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap_err();
249 assert!(
250 matches!(err, ssz::DecodeError::BytesInvalid(message) if message.contains("BlobAndProofV2"))
251 );
252 }
253
254 #[test]
255 fn ssz_blob_cells_and_proofs_v1_roundtrip() {
256 let blob_cells = vec![
257 Some(Cell::repeat_byte(0x01)),
258 None,
259 Some(Cell::repeat_byte(0x03)),
260 Some(Cell::repeat_byte(0x04)),
261 ];
262 let proofs = vec![
263 Some(Bytes48::repeat_byte(0x11)),
264 None,
265 Some(Bytes48::repeat_byte(0x33)),
266 Some(Bytes48::repeat_byte(0x44)),
267 ];
268 let blob_cells_and_proofs = BlobCellsAndProofsV1 { blob_cells, proofs };
269
270 let encoded = ssz::Encode::as_ssz_bytes(&blob_cells_and_proofs);
271 let decoded = <BlobCellsAndProofsV1 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap();
272 assert_eq!(decoded, blob_cells_and_proofs);
273 }
274
275 #[test]
276 fn ssz_blob_cells_and_proofs_v1_rejects_too_many_cells() {
277 let blob_cells_and_proofs = BlobCellsAndProofsV1 {
278 blob_cells: vec![Some(Cell::ZERO); CELLS_PER_EXT_BLOB + 1],
279 proofs: vec![Some(Bytes48::ZERO); CELLS_PER_EXT_BLOB + 1],
280 };
281 let encoded = ssz::Encode::as_ssz_bytes(&blob_cells_and_proofs);
282
283 let err = <BlobCellsAndProofsV1 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap_err();
284 assert!(
285 matches!(err, ssz::DecodeError::BytesInvalid(message) if message.contains("expected at most"))
286 );
287 }
288
289 #[test]
290 fn ssz_blob_cells_and_proofs_v1_rejects_mismatched_lengths() {
291 let blob_cells_and_proofs = BlobCellsAndProofsV1 {
292 blob_cells: vec![Some(Cell::ZERO)],
293 proofs: vec![Some(Bytes48::ZERO), Some(Bytes48::ZERO)],
294 };
295 let encoded = ssz::Encode::as_ssz_bytes(&blob_cells_and_proofs);
296
297 let err = <BlobCellsAndProofsV1 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap_err();
298 assert!(
299 matches!(err, ssz::DecodeError::BytesInvalid(message) if message.contains("does not match"))
300 );
301 }
302
303 #[test]
304 fn ssz_blob_cells_and_proofs_v1_rejects_mismatched_null_positions() {
305 let blob_cells_and_proofs = BlobCellsAndProofsV1 {
306 blob_cells: vec![Some(Cell::ZERO), None],
307 proofs: vec![None, Some(Bytes48::ZERO)],
308 };
309 let encoded = ssz::Encode::as_ssz_bytes(&blob_cells_and_proofs);
310
311 let err = <BlobCellsAndProofsV1 as ssz::Decode>::from_ssz_bytes(&encoded).unwrap_err();
312 assert!(
313 matches!(err, ssz::DecodeError::BytesInvalid(message) if message.contains("matching null positions"))
314 );
315 }
316}
317
318#[cfg(all(test, feature = "serde"))]
319mod serde_tests {
320 use super::*;
321
322 #[test]
323 fn blob_cells_and_proofs_v1_uses_spec_field_name() {
324 let blob_cells_and_proofs = BlobCellsAndProofsV1 {
325 blob_cells: vec![Some(Cell::ZERO), None],
326 proofs: vec![Some(Bytes48::ZERO), None],
327 };
328
329 let json = serde_json::to_string(&blob_cells_and_proofs).unwrap();
330 assert!(json.contains("\"blob_cells\""));
331 assert!(!json.contains("\"blobCells\""));
332 }
333}