alloy_eips/eip7594/
sidecar.rs

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