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