alloy_eips/eip7594/
sidecar.rs

1use crate::{
2    eip4844::{
3        kzg_to_versioned_hash, Blob, BlobAndProofV2, BlobTransactionSidecar, Bytes48,
4        BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_PROOF,
5    },
6    eip7594::{CELLS_PER_EXT_BLOB, EIP_7594_WRAPPER_VERSION},
7};
8use alloc::{boxed::Box, vec::Vec};
9use alloy_primitives::B256;
10use alloy_rlp::{BufMut, Decodable, Encodable, Header};
11
12#[cfg(feature = "kzg")]
13use crate::eip4844::BlobTransactionValidationError;
14
15use super::{Decodable7594, Encodable7594};
16
17/// This represents a set of blobs, and its corresponding commitments and proofs.
18/// Proof type depends on the sidecar variant.
19///
20/// This type encodes and decodes the fields without an rlp header.
21#[derive(Clone, PartialEq, Eq, Hash, Debug, derive_more::From)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23#[cfg_attr(feature = "serde", serde(untagged))]
24#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
25pub enum BlobTransactionSidecarVariant {
26    /// EIP-4844 style blob transaction sidecar.
27    Eip4844(BlobTransactionSidecar),
28    /// EIP-7594 style blob transaction sidecar with cell proofs.
29    Eip7594(BlobTransactionSidecarEip7594),
30}
31
32impl BlobTransactionSidecarVariant {
33    /// Returns true if this is a [`BlobTransactionSidecarVariant::Eip4844`].
34    pub const fn is_eip4844(&self) -> bool {
35        matches!(self, Self::Eip4844(_))
36    }
37
38    /// Returns true if this is a [`BlobTransactionSidecarVariant::Eip7594`].
39    pub const fn is_eip7594(&self) -> bool {
40        matches!(self, Self::Eip7594(_))
41    }
42
43    /// Returns the EIP-4844 sidecar if it is [`Self::Eip4844`].
44    pub const fn as_eip4844(&self) -> Option<&BlobTransactionSidecar> {
45        match self {
46            Self::Eip4844(sidecar) => Some(sidecar),
47            _ => None,
48        }
49    }
50
51    /// Returns the EIP-7594 sidecar if it is [`Self::Eip7594`].
52    pub const fn as_eip7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
53        match self {
54            Self::Eip7594(sidecar) => Some(sidecar),
55            _ => None,
56        }
57    }
58
59    /// Converts into EIP-4844 sidecar if it is [`Self::Eip4844`].
60    pub fn into_eip4844(self) -> Option<BlobTransactionSidecar> {
61        match self {
62            Self::Eip4844(sidecar) => Some(sidecar),
63            _ => None,
64        }
65    }
66
67    /// Converts the EIP-7594 sidecar if it is [`Self::Eip7594`].
68    pub fn into_eip7594(self) -> Option<BlobTransactionSidecarEip7594> {
69        match self {
70            Self::Eip7594(sidecar) => Some(sidecar),
71            _ => None,
72        }
73    }
74
75    /// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecarVariant].
76    #[inline]
77    pub fn size(&self) -> usize {
78        match self {
79            Self::Eip4844(sidecar) => sidecar.size(),
80            Self::Eip7594(sidecar) => sidecar.size(),
81        }
82    }
83
84    /// Verifies that the sidecar is valid. See relevant methods for each variant for more info.
85    #[cfg(feature = "kzg")]
86    pub fn validate(
87        &self,
88        blob_versioned_hashes: &[B256],
89        proof_settings: &c_kzg::KzgSettings,
90    ) -> Result<(), BlobTransactionValidationError> {
91        match self {
92            Self::Eip4844(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
93            Self::Eip7594(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
94        }
95    }
96
97    /// Returns an iterator over the versioned hashes of the commitments.
98    pub fn versioned_hashes(&self) -> Vec<B256> {
99        match self {
100            Self::Eip4844(sidecar) => sidecar.versioned_hashes().collect(),
101            Self::Eip7594(sidecar) => sidecar.versioned_hashes().collect(),
102        }
103    }
104
105    /// Outputs the RLP length of the [BlobTransactionSidecarVariant] fields, without a RLP header.
106    #[doc(hidden)]
107    pub fn rlp_encoded_fields_length(&self) -> usize {
108        match self {
109            Self::Eip4844(sidecar) => sidecar.rlp_encoded_fields_length(),
110            Self::Eip7594(sidecar) => sidecar.rlp_encoded_fields_length(),
111        }
112    }
113
114    /// Encodes the inner [BlobTransactionSidecarVariant] fields as RLP bytes, __without__ a RLP
115    /// header.
116    #[inline]
117    #[doc(hidden)]
118    pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
119        match self {
120            Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
121            Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
122        }
123    }
124
125    /// RLP decode the fields of a [BlobTransactionSidecarVariant] based on the wrapper version.
126    #[doc(hidden)]
127    pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
128        Self::decode_7594(buf)
129    }
130}
131
132impl Encodable for BlobTransactionSidecarVariant {
133    /// Encodes the [BlobTransactionSidecar] fields as RLP bytes, without a RLP header.
134    fn encode(&self, out: &mut dyn BufMut) {
135        match self {
136            Self::Eip4844(sidecar) => sidecar.encode(out),
137            Self::Eip7594(sidecar) => sidecar.encode(out),
138        }
139    }
140
141    fn length(&self) -> usize {
142        match self {
143            Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
144            Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
145        }
146    }
147}
148
149impl Decodable for BlobTransactionSidecarVariant {
150    /// Decodes the inner [BlobTransactionSidecar] fields from RLP bytes, without a RLP header.
151    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
152        let header = Header::decode(buf)?;
153        if !header.list {
154            return Err(alloy_rlp::Error::UnexpectedString);
155        }
156        if buf.len() < header.payload_length {
157            return Err(alloy_rlp::Error::InputTooShort);
158        }
159        let remaining = buf.len();
160        let this = Self::rlp_decode_fields(buf)?;
161        if buf.len() + header.payload_length != remaining {
162            return Err(alloy_rlp::Error::UnexpectedLength);
163        }
164
165        Ok(this)
166    }
167}
168
169impl Encodable7594 for BlobTransactionSidecarVariant {
170    fn encode_7594_len(&self) -> usize {
171        self.rlp_encoded_fields_length()
172    }
173
174    fn encode_7594(&self, out: &mut dyn BufMut) {
175        self.rlp_encode_fields(out);
176    }
177}
178
179impl Decodable7594 for BlobTransactionSidecarVariant {
180    fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
181        if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
182            Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
183        } else {
184            Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
185        }
186    }
187}
188
189#[cfg(feature = "serde")]
190impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
191    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
192    where
193        D: serde::Deserializer<'de>,
194    {
195        #[derive(serde::Deserialize)]
196        #[serde(field_identifier, rename_all = "camelCase")]
197        enum Field {
198            Blobs,
199            Commitments,
200            Proofs,
201            CellProofs,
202        }
203
204        struct VariantVisitor;
205
206        impl<'de> serde::de::Visitor<'de> for VariantVisitor {
207            type Value = BlobTransactionSidecarVariant;
208
209            fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210                formatter
211                    .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
212            }
213
214            fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
215            where
216                M: serde::de::MapAccess<'de>,
217            {
218                let mut blobs = None;
219                let mut commitments = None;
220                let mut proofs = None;
221                let mut cell_proofs = None;
222
223                while let Some(key) = map.next_key()? {
224                    match key {
225                        Field::Blobs => blobs = Some(map.next_value()?),
226                        Field::Commitments => commitments = Some(map.next_value()?),
227                        Field::Proofs => proofs = Some(map.next_value()?),
228                        Field::CellProofs => cell_proofs = Some(map.next_value()?),
229                    }
230                }
231
232                let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
233                let commitments =
234                    commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
235
236                match (cell_proofs, proofs) {
237                    (Some(cp), None) => {
238                        Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
239                            blobs,
240                            commitments,
241                            cell_proofs: cp,
242                        }))
243                    }
244                    (None, Some(pf)) => {
245                        Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
246                            blobs,
247                            commitments,
248                            proofs: pf,
249                        }))
250                    }
251                    (None, None) => {
252                        Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
253                    }
254                    (Some(_), Some(_)) => Err(serde::de::Error::custom(
255                        "Both 'cellProofs' and 'proofs' cannot be present",
256                    )),
257                }
258            }
259        }
260
261        const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
262        deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
263    }
264}
265
266/// This represents a set of blobs, and its corresponding commitments and cell proofs.
267///
268/// This type encodes and decodes the fields without an rlp header.
269#[derive(Clone, Default, PartialEq, Eq, Hash)]
270#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
271#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
272#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
273pub struct BlobTransactionSidecarEip7594 {
274    /// The blob data.
275    #[cfg_attr(
276        all(debug_assertions, feature = "serde"),
277        serde(deserialize_with = "crate::eip4844::deserialize_blobs")
278    )]
279    pub blobs: Vec<Blob>,
280    /// The blob commitments.
281    pub commitments: Vec<Bytes48>,
282    /// List of cell proofs for all blobs in the sidecar, including the proofs for the extension
283    /// indices, for a total of `CELLS_PER_EXT_BLOB` proofs per blob (`CELLS_PER_EXT_BLOB` is the
284    /// number of cells for an extended blob, defined in
285    /// [the consensus specs](https://github.com/ethereum/consensus-specs/tree/9d377fd53d029536e57cfda1a4d2c700c59f86bf/specs/fulu/polynomial-commitments-sampling.md#cells))
286    pub cell_proofs: Vec<Bytes48>,
287}
288
289impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
290    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
291        f.debug_struct("BlobTransactionSidecarEip7594")
292            .field("blobs", &self.blobs.len())
293            .field("commitments", &self.commitments)
294            .field("cell_proofs", &self.cell_proofs)
295            .finish()
296    }
297}
298
299impl BlobTransactionSidecarEip7594 {
300    /// Constructs a new [BlobTransactionSidecarEip7594] from a set of blobs, commitments, and
301    /// cell proofs.
302    pub const fn new(
303        blobs: Vec<Blob>,
304        commitments: Vec<Bytes48>,
305        cell_proofs: Vec<Bytes48>,
306    ) -> Self {
307        Self { blobs, commitments, cell_proofs }
308    }
309
310    /// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecarEip7594].
311    #[inline]
312    pub fn size(&self) -> usize {
313        self.blobs.len() * BYTES_PER_BLOB + // blobs
314               self.commitments.len() * BYTES_PER_COMMITMENT + // commitments
315               self.cell_proofs.len() * BYTES_PER_PROOF // proofs
316    }
317
318    /// Verifies that the versioned hashes are valid for this sidecar's blob data, commitments, and
319    /// proofs.
320    ///
321    /// Takes as input the [KzgSettings](c_kzg::KzgSettings), which should contain the parameters
322    /// derived from the KZG trusted setup.
323    ///
324    /// This ensures that the blob transaction payload has the expected number of blob data
325    /// elements, commitments, and proofs. The cells are constructed from each blob and verified
326    /// against the commitments and proofs.
327    ///
328    /// Returns [BlobTransactionValidationError::InvalidProof] if any blob KZG proof in the response
329    /// fails to verify, or if the versioned hashes in the transaction do not match the actual
330    /// commitment versioned hashes.
331    #[cfg(feature = "kzg")]
332    pub fn validate(
333        &self,
334        blob_versioned_hashes: &[B256],
335        proof_settings: &c_kzg::KzgSettings,
336    ) -> Result<(), BlobTransactionValidationError> {
337        // Ensure the versioned hashes and commitments have the same length.
338        if blob_versioned_hashes.len() != self.commitments.len() {
339            return Err(c_kzg::Error::MismatchLength(format!(
340                "There are {} versioned commitment hashes and {} commitments",
341                blob_versioned_hashes.len(),
342                self.commitments.len()
343            ))
344            .into());
345        }
346
347        let blobs_len = self.blobs.len();
348        let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
349        if self.cell_proofs.len() != expected_cell_proofs_len {
350            return Err(c_kzg::Error::MismatchLength(format!(
351                "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
352                self.cell_proofs.len(),
353                blobs_len,
354                expected_cell_proofs_len
355            ))
356            .into());
357        }
358
359        // calculate versioned hashes by zipping & iterating
360        for (versioned_hash, commitment) in
361            blob_versioned_hashes.iter().zip(self.commitments.iter())
362        {
363            let commitment = c_kzg::KzgCommitment::from(commitment.0);
364
365            // calculate & verify versioned hash
366            let calculated_versioned_hash = kzg_to_versioned_hash(commitment.as_slice());
367            if *versioned_hash != calculated_versioned_hash {
368                return Err(BlobTransactionValidationError::WrongVersionedHash {
369                    have: *versioned_hash,
370                    expected: calculated_versioned_hash,
371                });
372            }
373        }
374
375        // Repeat cell ranges for each blob.
376        let cell_indices =
377            Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
378
379        // Repeat commitments for each cell.
380        let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
381        for commitment in &self.commitments {
382            commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
383        }
384
385        // SAFETY: ALL types have the same size
386        let res = unsafe {
387            let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
388            for blob in &self.blobs {
389                let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
390                cells.extend(proof_settings.compute_cells(blob)?.into_iter());
391            }
392
393            proof_settings.verify_cell_kzg_proof_batch(
394                // commitments
395                core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
396                // cell indices
397                &cell_indices,
398                // cells
399                &cells,
400                // proofs
401                core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
402            )?
403        };
404
405        res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
406    }
407
408    /// Returns an iterator over the versioned hashes of the commitments.
409    pub fn versioned_hashes(&self) -> impl Iterator<Item = B256> + '_ {
410        self.commitments.iter().map(|c| kzg_to_versioned_hash(c.as_slice()))
411    }
412
413    /// Matches versioned hashes and returns an iterator of (index, [`BlobAndProofV2`]) pairs
414    /// where index is the position in `versioned_hashes` that matched the versioned hash in the
415    /// sidecar.
416    ///
417    /// This is used for the `engine_getBlobsV2` RPC endpoint of the engine API
418    pub fn match_versioned_hashes<'a>(
419        &'a self,
420        versioned_hashes: &'a [B256],
421    ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
422        self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
423            versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
424                if blob_versioned_hash == *target_hash {
425                    let maybe_blob = self.blobs.get(i);
426                    let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
427                    let maybe_proofs = Some(&self.cell_proofs[proof_range])
428                        .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
429                    if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
430                        return Some((
431                            j,
432                            BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
433                        ));
434                    }
435                }
436                None
437            })
438        })
439    }
440
441    /// Outputs the RLP length of [BlobTransactionSidecarEip7594] fields without a RLP header.
442    #[doc(hidden)]
443    pub fn rlp_encoded_fields_length(&self) -> usize {
444        // wrapper version + blobs + commitments + cell proofs
445        1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
446    }
447
448    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, __without__ a
449    /// RLP header.
450    ///
451    /// This encodes the fields in the following order:
452    /// - `wrapper_version`
453    /// - `blobs`
454    /// - `commitments`
455    /// - `cell_proofs`
456    #[inline]
457    #[doc(hidden)]
458    pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
459        // Put version byte.
460        out.put_u8(EIP_7594_WRAPPER_VERSION);
461        // Encode the blobs, commitments, and cell proofs
462        self.blobs.encode(out);
463        self.commitments.encode(out);
464        self.cell_proofs.encode(out);
465    }
466
467    /// Creates an RLP header for the [BlobTransactionSidecarEip7594].
468    fn rlp_header(&self) -> Header {
469        Header { list: true, payload_length: self.rlp_encoded_fields_length() }
470    }
471
472    /// Calculates the length of the [BlobTransactionSidecarEip7594] when encoded as
473    /// RLP.
474    pub fn rlp_encoded_length(&self) -> usize {
475        self.rlp_header().length() + self.rlp_encoded_fields_length()
476    }
477
478    /// Encodes the [BlobTransactionSidecarEip7594] as RLP bytes.
479    pub fn rlp_encode(&self, out: &mut dyn BufMut) {
480        self.rlp_header().encode(out);
481        self.rlp_encode_fields(out);
482    }
483
484    /// RLP decode the fields of a [BlobTransactionSidecarEip7594].
485    #[doc(hidden)]
486    pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
487        Ok(Self {
488            blobs: Decodable::decode(buf)?,
489            commitments: Decodable::decode(buf)?,
490            cell_proofs: Decodable::decode(buf)?,
491        })
492    }
493
494    /// Decodes the [BlobTransactionSidecarEip7594] from RLP bytes.
495    pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
496        let header = Header::decode(buf)?;
497        if !header.list {
498            return Err(alloy_rlp::Error::UnexpectedString);
499        }
500        if buf.len() < header.payload_length {
501            return Err(alloy_rlp::Error::InputTooShort);
502        }
503        let remaining = buf.len();
504
505        let this = Self::decode_7594(buf)?;
506        if buf.len() + header.payload_length != remaining {
507            return Err(alloy_rlp::Error::UnexpectedLength);
508        }
509
510        Ok(this)
511    }
512}
513
514impl Encodable for BlobTransactionSidecarEip7594 {
515    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, without a RLP header.
516    fn encode(&self, out: &mut dyn BufMut) {
517        self.rlp_encode(out);
518    }
519
520    fn length(&self) -> usize {
521        self.rlp_encoded_length()
522    }
523}
524
525impl Decodable for BlobTransactionSidecarEip7594 {
526    /// Decodes the inner [BlobTransactionSidecarEip7594] fields from RLP bytes, without a RLP
527    /// header.
528    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
529        Self::rlp_decode(buf)
530    }
531}
532
533impl Encodable7594 for BlobTransactionSidecarEip7594 {
534    fn encode_7594_len(&self) -> usize {
535        self.rlp_encoded_fields_length()
536    }
537
538    fn encode_7594(&self, out: &mut dyn BufMut) {
539        self.rlp_encode_fields(out);
540    }
541}
542
543impl Decodable7594 for BlobTransactionSidecarEip7594 {
544    fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
545        let wrapper_version: u8 = Decodable::decode(buf)?;
546        if wrapper_version != EIP_7594_WRAPPER_VERSION {
547            return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
548        }
549        Self::rlp_decode_fields(buf)
550    }
551}
552
553#[cfg(test)]
554mod tests {
555    use super::*;
556
557    #[test]
558    fn sidecar_variant_rlp_roundtrip() {
559        let mut encoded = Vec::new();
560
561        // 4844
562        let empty_sidecar_4844 =
563            BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
564        empty_sidecar_4844.encode(&mut encoded);
565        assert_eq!(
566            empty_sidecar_4844,
567            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
568        );
569
570        let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
571            vec![Blob::default()],
572            vec![Bytes48::ZERO],
573            vec![Bytes48::ZERO],
574        ));
575        encoded.clear();
576        sidecar_4844.encode(&mut encoded);
577        assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
578
579        // 7594
580        let empty_sidecar_7594 =
581            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
582        encoded.clear();
583        empty_sidecar_7594.encode(&mut encoded);
584        assert_eq!(
585            empty_sidecar_7594,
586            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
587        );
588
589        let sidecar_7594 =
590            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
591                vec![Blob::default()],
592                vec![Bytes48::ZERO],
593                core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
594            ));
595        encoded.clear();
596        sidecar_7594.encode(&mut encoded);
597        assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
598    }
599
600    #[test]
601    #[cfg(feature = "serde")]
602    fn sidecar_variant_json_deserialize_sanity() {
603        let eip4844 = BlobTransactionSidecar::default();
604        let json = serde_json::to_string(&eip4844).unwrap();
605        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
606        assert!(variant.is_eip4844());
607        let jsonvariant = serde_json::to_string(&variant).unwrap();
608        assert_eq!(json, jsonvariant);
609
610        let eip7594 = BlobTransactionSidecarEip7594::default();
611        let json = serde_json::to_string(&eip7594).unwrap();
612        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
613        assert!(variant.is_eip7594());
614        let jsonvariant = serde_json::to_string(&variant).unwrap();
615        assert_eq!(json, jsonvariant);
616    }
617
618    #[test]
619    fn rlp_7594_roundtrip() {
620        let mut encoded = Vec::new();
621
622        let sidecar_4844 = BlobTransactionSidecar::default();
623        sidecar_4844.encode_7594(&mut encoded);
624        assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
625
626        let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
627        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
628        encoded.clear();
629        sidecar_variant_4844.encode_7594(&mut encoded);
630        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
631
632        let sidecar_7594 = BlobTransactionSidecarEip7594::default();
633        encoded.clear();
634        sidecar_7594.encode_7594(&mut encoded);
635        assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
636
637        let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
638        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
639        encoded.clear();
640        sidecar_variant_7594.encode_7594(&mut encoded);
641        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
642    }
643}