Skip to main content

alloy_eips/eip4844/
mod.rs

1//! [EIP-4844] constants and helpers.
2//!
3//! [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
4
5/// Re-export the `c_kzg` crate for downstream consumers.
6#[cfg(feature = "kzg")]
7pub use c_kzg;
8
9/// Module houses the KZG settings, enabling Custom and Default
10#[cfg(feature = "kzg")]
11pub mod env_settings;
12/// This module contains functions and types used for parsing and utilizing the [Trusted Setup]( https://ceremony.ethereum.org/) for the `KzgSettings`.
13#[cfg(feature = "kzg")]
14pub mod trusted_setup_points;
15
16/// Builder and utils for the [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction)
17pub mod builder;
18pub mod utils;
19
20mod engine;
21pub use engine::*;
22
23/// Contains sidecar related types
24#[cfg(feature = "kzg-sidecar")]
25mod sidecar;
26#[cfg(feature = "kzg-sidecar")]
27pub use sidecar::*;
28
29use alloy_primitives::{b256, Bytes, FixedBytes, B256, U256, U512};
30
31use crate::eip7840;
32
33/// The modulus of the BLS group used in the KZG commitment scheme. All field
34/// elements contained in a blob MUST be STRICTLY LESS than this value.
35pub const BLS_MODULUS_BYTES: B256 =
36    b256!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");
37
38/// The modulus of the BLS group used in the KZG commitment scheme. All field
39/// elements contained in a blob MUST be STRICTLY LESS than this value.
40pub const BLS_MODULUS: U256 = U256::from_be_bytes(BLS_MODULUS_BYTES.0);
41
42/// Size a single field element in bytes.
43pub const FIELD_ELEMENT_BYTES: u64 = 32;
44
45/// Size a single field element in bytes.
46pub const FIELD_ELEMENT_BYTES_USIZE: usize = FIELD_ELEMENT_BYTES as usize;
47
48/// How many field elements are stored in a single data blob.
49pub const FIELD_ELEMENTS_PER_BLOB: u64 = 4096;
50
51/// Number of usable bits in a field element. The top two bits are always zero.
52pub const USABLE_BITS_PER_FIELD_ELEMENT: usize = 254;
53
54/// The number of usable bytes in a single data blob. This is the number of
55/// bytes you can encode in a blob without any field element being >=
56/// [`BLS_MODULUS`].
57pub const USABLE_BYTES_PER_BLOB: usize =
58    USABLE_BITS_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB as usize / 8;
59
60/// Gas consumption of a single data blob.
61pub const DATA_GAS_PER_BLOB: u64 = 131_072u64; // 32*4096 = 131072 == 2^17 == 0x20000
62
63/// How many bytes are in a blob
64/// Same as [DATA_GAS_PER_BLOB], but as an usize
65pub const BYTES_PER_BLOB: usize = 131_072;
66
67/// Maximum data gas for data blobs in a single block.
68pub const MAX_DATA_GAS_PER_BLOCK_DENCUN: u64 = 786_432u64; // 0xC0000 = 6 * 0x20000
69
70/// Target data gas for data blobs in a single block.
71pub const TARGET_DATA_GAS_PER_BLOCK_DENCUN: u64 = 393_216u64; // 0x60000 = 3 * 0x20000
72
73/// Maximum number of data blobs in a single block.
74pub const MAX_BLOBS_PER_BLOCK_DENCUN: usize =
75    (MAX_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) as usize; // 786432 / 131072  = 6
76
77/// Target number of data blobs in a single block.
78pub const TARGET_BLOBS_PER_BLOCK_DENCUN: u64 = TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB; // 393216 / 131072 = 3
79
80/// Determines the maximum rate of change for blob fee
81pub const BLOB_GASPRICE_UPDATE_FRACTION: u128 = 3_338_477u128; // 3338477
82
83/// Minimum gas price for a data blob
84pub const BLOB_TX_MIN_BLOB_GASPRICE: u128 = 1u128;
85
86/// Commitment version of a KZG commitment
87pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
88
89/// How many bytes are in a commitment
90pub const BYTES_PER_COMMITMENT: usize = 48;
91
92/// How many bytes are in a proof
93pub const BYTES_PER_PROOF: usize = 48;
94
95/// A Blob serialized as 0x-prefixed hex string
96pub type Blob = FixedBytes<BYTES_PER_BLOB>;
97
98/// Helper function to deserialize boxed blobs.
99#[cfg(feature = "serde")]
100pub fn deserialize_blob<'de, D>(deserializer: D) -> Result<alloc::boxed::Box<Blob>, D::Error>
101where
102    D: serde::de::Deserializer<'de>,
103{
104    use serde::Deserialize;
105    let raw_blob = <alloy_primitives::Bytes>::deserialize(deserializer)?;
106    let blob = alloc::boxed::Box::new(
107        Blob::try_from(raw_blob.as_ref()).map_err(serde::de::Error::custom)?,
108    );
109    Ok(blob)
110}
111
112/// Helper function to deserialize boxed blobs from a serde deserializer.
113#[cfg(all(debug_assertions, feature = "serde"))]
114pub fn deserialize_blobs<'de, D>(deserializer: D) -> Result<alloc::vec::Vec<Blob>, D::Error>
115where
116    D: serde::de::Deserializer<'de>,
117{
118    use alloc::vec::Vec;
119    use serde::Deserialize;
120
121    let raw_blobs = Vec::<alloy_primitives::Bytes>::deserialize(deserializer)?;
122    let mut blobs = Vec::with_capacity(raw_blobs.len());
123    for blob in raw_blobs {
124        blobs.push(Blob::try_from(blob.as_ref()).map_err(serde::de::Error::custom)?);
125    }
126    Ok(blobs)
127}
128
129#[cfg(all(not(debug_assertions), feature = "serde"))]
130#[inline(always)]
131/// Helper function to deserialize boxed blobs from a serde deserializer.
132pub fn deserialize_blobs<'de, D>(deserializer: D) -> Result<alloc::vec::Vec<Blob>, D::Error>
133where
134    D: serde::de::Deserializer<'de>,
135{
136    serde::Deserialize::deserialize(deserializer)
137}
138
139/// A heap allocated blob that serializes as 0x-prefixed hex string
140#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, alloy_rlp::RlpEncodableWrapper)]
141pub struct HeapBlob(Bytes);
142
143impl HeapBlob {
144    /// Create a new heap blob from a byte slice.
145    pub fn new(blob: &[u8]) -> Result<Self, InvalidBlobLength> {
146        if blob.len() != BYTES_PER_BLOB {
147            return Err(InvalidBlobLength(blob.len()));
148        }
149
150        Ok(Self(Bytes::copy_from_slice(blob)))
151    }
152
153    /// Create a new heap blob from an array.
154    pub fn from_array(blob: [u8; BYTES_PER_BLOB]) -> Self {
155        Self(Bytes::from(blob))
156    }
157
158    /// Create a new heap blob from [`Bytes`].
159    pub fn from_bytes(bytes: Bytes) -> Result<Self, InvalidBlobLength> {
160        if bytes.len() != BYTES_PER_BLOB {
161            return Err(InvalidBlobLength(bytes.len()));
162        }
163
164        Ok(Self(bytes))
165    }
166
167    /// Generate a new heap blob with all bytes set to `byte`.
168    pub fn repeat_byte(byte: u8) -> Self {
169        Self(Bytes::from(vec![byte; BYTES_PER_BLOB]))
170    }
171
172    /// Get the inner
173    pub const fn inner(&self) -> &Bytes {
174        &self.0
175    }
176}
177
178impl Default for HeapBlob {
179    fn default() -> Self {
180        Self::repeat_byte(0)
181    }
182}
183
184/// Error indicating that the blob length is invalid.
185#[derive(Debug, Clone)]
186pub struct InvalidBlobLength(usize);
187impl core::fmt::Display for InvalidBlobLength {
188    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
189        write!(f, "Invalid blob length: {}, expected: {BYTES_PER_BLOB}", self.0)
190    }
191}
192impl core::error::Error for InvalidBlobLength {}
193
194#[cfg(feature = "serde")]
195impl serde::Serialize for HeapBlob {
196    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
197    where
198        S: serde::Serializer,
199    {
200        self.inner().serialize(serializer)
201    }
202}
203
204#[cfg(any(test, feature = "arbitrary"))]
205impl<'a> arbitrary::Arbitrary<'a> for HeapBlob {
206    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
207        let mut blob = vec![0u8; BYTES_PER_BLOB];
208        u.fill_buffer(&mut blob)?;
209        Ok(Self(Bytes::from(blob)))
210    }
211}
212
213#[cfg(feature = "serde")]
214impl<'de> serde::Deserialize<'de> for HeapBlob {
215    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
216    where
217        D: serde::de::Deserializer<'de>,
218    {
219        let inner = <Bytes>::deserialize(deserializer)?;
220
221        Self::from_bytes(inner).map_err(serde::de::Error::custom)
222    }
223}
224
225impl alloy_rlp::Decodable for HeapBlob {
226    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
227        let bytes = <Bytes>::decode(buf)?;
228
229        Self::from_bytes(bytes).map_err(|_| alloy_rlp::Error::Custom("invalid blob length"))
230    }
231}
232
233/// A commitment/proof serialized as 0x-prefixed hex string
234pub type Bytes48 = FixedBytes<48>;
235
236/// Conversion helpers for c-kzg byte wrappers.
237#[cfg(feature = "kzg")]
238pub trait AsCkzg: Sized {
239    /// The equivalent c-kzg byte wrapper type.
240    type Ckzg;
241
242    /// Returns this value as its c-kzg equivalent.
243    fn as_ckzg(&self) -> &Self::Ckzg;
244
245    /// Returns this value as its mutable c-kzg equivalent.
246    fn as_ckzg_mut(&mut self) -> &mut Self::Ckzg;
247
248    /// Converts a c-kzg value into this type.
249    fn from_ckzg(value: Self::Ckzg) -> Self;
250
251    /// Returns this slice as its c-kzg equivalent.
252    fn slice_as_ckzg(slice: &[Self]) -> &[Self::Ckzg];
253
254    /// Returns this slice as its mutable c-kzg equivalent.
255    fn slice_as_ckzg_mut(slice: &mut [Self]) -> &mut [Self::Ckzg];
256
257    /// Converts this vector into its c-kzg equivalent.
258    fn vec_as_ckzg(vec: alloc::vec::Vec<Self>) -> alloc::vec::Vec<Self::Ckzg>;
259
260    /// Converts a c-kzg vector into this type's equivalent vector.
261    fn vec_from_ckzg(vec: alloc::vec::Vec<Self::Ckzg>) -> alloc::vec::Vec<Self>;
262}
263
264/// Conversion helpers for c-kzg byte wrappers back to Alloy byte wrappers.
265#[cfg(feature = "kzg")]
266pub trait AsAlloy: Sized {
267    /// The equivalent Alloy byte wrapper type.
268    type Alloy;
269
270    /// Returns this value as its Alloy equivalent.
271    fn as_alloy(&self) -> &Self::Alloy;
272
273    /// Returns this value as its mutable Alloy equivalent.
274    fn as_alloy_mut(&mut self) -> &mut Self::Alloy;
275
276    /// Converts this value into its Alloy equivalent.
277    fn into_alloy(self) -> Self::Alloy;
278
279    /// Returns this slice as its Alloy equivalent.
280    fn slice_as_alloy(slice: &[Self]) -> &[Self::Alloy];
281
282    /// Returns this slice as its mutable Alloy equivalent.
283    fn slice_as_alloy_mut(slice: &mut [Self]) -> &mut [Self::Alloy];
284
285    /// Converts this vector into its Alloy equivalent.
286    fn vec_as_alloy(vec: alloc::vec::Vec<Self>) -> alloc::vec::Vec<Self::Alloy>;
287
288    /// Converts this boxed slice into its Alloy equivalent.
289    fn boxed_slice_as_alloy(boxed: alloc::boxed::Box<[Self]>) -> alloc::boxed::Box<[Self::Alloy]>;
290
291    /// Converts an Alloy vector into this type's equivalent vector.
292    fn vec_from_alloy(vec: alloc::vec::Vec<Self::Alloy>) -> alloc::vec::Vec<Self>;
293}
294
295#[cfg(feature = "kzg")]
296macro_rules! impl_ckzg_conversions {
297    ($alloy:ty, $ckzg:ty) => {
298        impl AsCkzg for $alloy {
299            type Ckzg = $ckzg;
300
301            #[inline]
302            fn as_ckzg(&self) -> &Self::Ckzg {
303                // SAFETY: This macro is only invoked for transparent byte wrappers with the same
304                // layout and alignment as their c-kzg equivalents.
305                unsafe { core::mem::transmute(self) }
306            }
307
308            #[inline]
309            fn as_ckzg_mut(&mut self) -> &mut Self::Ckzg {
310                // SAFETY: See `AsCkzg::as_ckzg`.
311                unsafe { core::mem::transmute(self) }
312            }
313
314            #[inline]
315            fn from_ckzg(value: Self::Ckzg) -> Self {
316                // SAFETY: See `AsCkzg::as_ckzg`.
317                unsafe { core::mem::transmute(value) }
318            }
319
320            #[inline]
321            fn slice_as_ckzg(slice: &[Self]) -> &[Self::Ckzg] {
322                // SAFETY: See `AsCkzg::as_ckzg`.
323                unsafe { core::mem::transmute(slice) }
324            }
325
326            #[inline]
327            fn slice_as_ckzg_mut(slice: &mut [Self]) -> &mut [Self::Ckzg] {
328                // SAFETY: See `AsCkzg::as_ckzg`.
329                unsafe { core::mem::transmute(slice) }
330            }
331
332            #[inline]
333            fn vec_as_ckzg(vec: alloc::vec::Vec<Self>) -> alloc::vec::Vec<Self::Ckzg> {
334                // SAFETY: See `AsCkzg::as_ckzg`.
335                unsafe { core::mem::transmute(vec) }
336            }
337
338            #[inline]
339            fn vec_from_ckzg(vec: alloc::vec::Vec<Self::Ckzg>) -> alloc::vec::Vec<Self> {
340                // SAFETY: See `AsCkzg::as_ckzg`.
341                unsafe { core::mem::transmute(vec) }
342            }
343        }
344
345        impl_ckzg_conversions!(reverse $alloy, $ckzg);
346    };
347    (reverse $alloy:ty, $ckzg:ty) => {
348        impl AsAlloy for $ckzg {
349            type Alloy = $alloy;
350
351            #[inline]
352            fn as_alloy(&self) -> &Self::Alloy {
353                // SAFETY: This macro is only invoked for c-kzg byte wrappers with the same layout
354                // and alignment as their Alloy equivalents.
355                unsafe { core::mem::transmute(self) }
356            }
357
358            #[inline]
359            fn as_alloy_mut(&mut self) -> &mut Self::Alloy {
360                // SAFETY: See `AsAlloy::as_alloy`.
361                unsafe { core::mem::transmute(self) }
362            }
363
364            #[inline]
365            fn into_alloy(self) -> Self::Alloy {
366                // SAFETY: See `AsAlloy::as_alloy`.
367                unsafe { core::mem::transmute(self) }
368            }
369
370            #[inline]
371            fn slice_as_alloy(slice: &[Self]) -> &[Self::Alloy] {
372                // SAFETY: See `AsAlloy::as_alloy`.
373                unsafe { core::mem::transmute(slice) }
374            }
375
376            #[inline]
377            fn slice_as_alloy_mut(slice: &mut [Self]) -> &mut [Self::Alloy] {
378                // SAFETY: See `AsAlloy::as_alloy`.
379                unsafe { core::mem::transmute(slice) }
380            }
381
382            #[inline]
383            fn vec_as_alloy(vec: alloc::vec::Vec<Self>) -> alloc::vec::Vec<Self::Alloy> {
384                // SAFETY: See `AsAlloy::as_alloy`.
385                unsafe { core::mem::transmute(vec) }
386            }
387
388            #[inline]
389            fn boxed_slice_as_alloy(
390                boxed: alloc::boxed::Box<[Self]>,
391            ) -> alloc::boxed::Box<[Self::Alloy]> {
392                // SAFETY: See `AsAlloy::as_alloy`.
393                unsafe {
394                    core::mem::transmute::<
395                        alloc::boxed::Box<[Self]>,
396                        alloc::boxed::Box<[Self::Alloy]>,
397                    >(boxed)
398                }
399            }
400
401            #[inline]
402            fn vec_from_alloy(vec: alloc::vec::Vec<Self::Alloy>) -> alloc::vec::Vec<Self> {
403                // SAFETY: See `AsAlloy::as_alloy`.
404                unsafe { core::mem::transmute(vec) }
405            }
406        }
407    };
408}
409
410#[cfg(feature = "kzg")]
411impl_ckzg_conversions!(Blob, c_kzg::Blob);
412#[cfg(feature = "kzg")]
413impl_ckzg_conversions!(Bytes48, c_kzg::Bytes48);
414#[cfg(feature = "kzg")]
415impl_ckzg_conversions!(reverse Bytes48, c_kzg::KzgProof);
416#[cfg(feature = "kzg")]
417impl_ckzg_conversions!(reverse crate::eip7594::Cell, c_kzg::Cell);
418
419/// Returns blobs as c-kzg blobs.
420#[cfg(feature = "kzg")]
421#[deprecated(note = "use `Blob::slice_as_ckzg` via the `AsCkzg` trait instead")]
422#[inline]
423pub fn blobs_as_ckzg(blobs: &[Blob]) -> &[c_kzg::Blob] {
424    Blob::slice_as_ckzg(blobs)
425}
426
427/// Returns commitment/proof bytes as c-kzg bytes.
428#[cfg(feature = "kzg")]
429#[deprecated(note = "use `Bytes48::slice_as_ckzg` via the `AsCkzg` trait instead")]
430#[inline]
431pub fn bytes48_as_ckzg(bytes: &[Bytes48]) -> &[c_kzg::Bytes48] {
432    Bytes48::slice_as_ckzg(bytes)
433}
434
435/// Converts c-kzg bytes into the Alloy 48-byte wrapper.
436#[cfg(feature = "kzg")]
437#[deprecated(note = "use `Bytes48::from_ckzg` via the `AsCkzg` trait instead")]
438#[inline]
439pub fn bytes48_from_ckzg(bytes: c_kzg::Bytes48) -> Bytes48 {
440    Bytes48::from_ckzg(bytes)
441}
442
443/// Calculates the versioned hash for a KzgCommitment of 48 bytes.
444///
445/// Specified in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension)
446///
447/// # Panics
448///
449/// If the given commitment is not 48 bytes long.
450#[cfg(feature = "sha2")]
451pub fn kzg_to_versioned_hash(commitment: &[u8]) -> B256 {
452    use sha2::Digest;
453
454    debug_assert_eq!(commitment.len(), 48, "commitment length is not 48");
455    let mut res = sha2::Sha256::digest(commitment);
456    res[0] = VERSIONED_HASH_VERSION_KZG;
457    B256::new(res.into())
458}
459
460/// Calculates the `excess_blob_gas` from the parent header's `blob_gas_used` and `excess_blob_gas`.
461///
462/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
463/// (`calc_excess_blob_gas`).
464#[inline]
465pub const fn calc_excess_blob_gas(parent_excess_blob_gas: u64, parent_blob_gas_used: u64) -> u64 {
466    let next_excess_blob_gas = parent_excess_blob_gas + parent_blob_gas_used;
467    next_excess_blob_gas.saturating_sub(TARGET_DATA_GAS_PER_BLOCK_DENCUN)
468}
469
470/// Calculates the blob gas price from the header's excess blob gas field.
471///
472/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
473/// (`get_blob_gasprice`).
474#[inline]
475pub fn calc_blob_gasprice(excess_blob_gas: u64) -> u128 {
476    eip7840::BlobParams::cancun().calc_blob_fee(excess_blob_gas)
477}
478
479/// Approximates `factor * e ** (numerator / denominator)` using Taylor expansion.
480///
481/// This is used to calculate the blob price.
482///
483/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
484/// (`fake_exponential`).
485///
486/// # Panics
487///
488/// This function panics if `denominator` is zero.
489#[inline]
490pub fn fake_exponential(factor: u128, numerator: u128, denominator: u128) -> u128 {
491    assert!(denominator != 0, "attempt to divide by zero");
492
493    let mut i = U512::from(1);
494    let denominator = U512::from(denominator);
495    let numerator = U512::from(numerator);
496    let mut output = U512::ZERO;
497    let mut numerator_accum = U512::from(factor) * denominator;
498
499    while numerator_accum > U512::ZERO {
500        let Some(next_output) = output.checked_add(numerator_accum) else {
501            return u128::MAX;
502        };
503        output = next_output;
504
505        // accum = (accum * numerator) / (denominator * i)
506        let Some(next_accum) = numerator_accum.checked_mul(numerator) else {
507            return u128::MAX;
508        };
509        numerator_accum = next_accum / (denominator * i);
510
511        i += U512::from(1);
512    }
513
514    (output / denominator).try_into().unwrap_or(u128::MAX)
515}
516
517#[cfg(test)]
518mod tests {
519    use super::*;
520
521    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L27
522    #[test]
523    fn test_calc_excess_blob_gas() {
524        const ZERO_EXCESS: u64 = calc_excess_blob_gas(0, 0);
525        assert_eq!(ZERO_EXCESS, 0);
526
527        for t @ &(excess, blobs, expected) in &[
528            // The excess blob gas should not increase from zero if the used blob
529            // slots are below - or equal - to the target.
530            (0, 0, 0),
531            (0, 1, 0),
532            (0, TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB, 0),
533            // If the target blob gas is exceeded, the excessBlobGas should increase
534            // by however much it was overshot
535            (0, (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) + 1, DATA_GAS_PER_BLOB),
536            (1, (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) + 1, DATA_GAS_PER_BLOB + 1),
537            (
538                1,
539                (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) + 2,
540                2 * DATA_GAS_PER_BLOB + 1,
541            ),
542            // The excess blob gas should decrease by however much the target was
543            // under-shot, capped at zero.
544            (
545                TARGET_DATA_GAS_PER_BLOCK_DENCUN,
546                TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB,
547                TARGET_DATA_GAS_PER_BLOCK_DENCUN,
548            ),
549            (
550                TARGET_DATA_GAS_PER_BLOCK_DENCUN,
551                (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) - 1,
552                TARGET_DATA_GAS_PER_BLOCK_DENCUN - DATA_GAS_PER_BLOB,
553            ),
554            (
555                TARGET_DATA_GAS_PER_BLOCK_DENCUN,
556                (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) - 2,
557                TARGET_DATA_GAS_PER_BLOCK_DENCUN - (2 * DATA_GAS_PER_BLOB),
558            ),
559            (DATA_GAS_PER_BLOB - 1, (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) - 1, 0),
560        ] {
561            let actual = calc_excess_blob_gas(excess, blobs * DATA_GAS_PER_BLOB);
562            assert_eq!(actual, expected, "test: {t:?}");
563
564            // the inlined implementation must stay consistent with the osaka variant it
565            // previously delegated to
566            let osaka = eip7840::BlobParams::cancun().next_block_excess_blob_gas_osaka(
567                excess,
568                blobs * DATA_GAS_PER_BLOB,
569                0,
570            );
571            assert_eq!(actual, osaka, "osaka consistency: {t:?}");
572        }
573    }
574
575    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L60
576    #[test]
577    fn test_calc_blob_fee() {
578        let blob_fee_vectors = &[
579            (0, 1),
580            (2314057, 1),
581            (2314058, 2),
582            (10 * 1024 * 1024, 23),
583            // calc_blob_gasprice approximates `e ** (excess_blob_gas /
584            // BLOB_GASPRICE_UPDATE_FRACTION)` using Taylor expansion
585            //
586            // to roughly find where boundaries will be hit:
587            // 2 ** bits = e ** (excess_blob_gas / BLOB_GASPRICE_UPDATE_FRACTION)
588            // excess_blob_gas = ln(2 ** bits) * BLOB_GASPRICE_UPDATE_FRACTION
589            (148099578, 18446739238971471609), // output is just below the overflow
590            (148099579, 18446744762204311910), // output is just after the overflow
591            (161087488, 902580055246494526580),
592        ];
593
594        for &(excess, expected) in blob_fee_vectors {
595            let actual = calc_blob_gasprice(excess);
596            assert_eq!(actual, expected, "test: {excess}");
597        }
598    }
599
600    #[test]
601    fn test_calc_blob_fee_pectra_large_input() {
602        let actual = crate::eip7691::calc_blob_gasprice(299453931);
603        assert_eq!(actual, 93359993185840258978230108);
604    }
605
606    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L78
607    #[test]
608    fn fake_exp() {
609        for t @ &(factor, numerator, denominator, expected) in &[
610            (1u64, 0u64, 1u64, 1u128),
611            (38493, 0, 1000, 38493),
612            (0, 1234, 2345, 0),
613            (1, 2, 1, 6), // approximate 7.389
614            (1, 4, 2, 6),
615            (1, 3, 1, 16), // approximate 20.09
616            (1, 6, 2, 18),
617            (1, 4, 1, 49), // approximate 54.60
618            (1, 8, 2, 50),
619            (10, 8, 2, 542), // approximate 540.598
620            (11, 8, 2, 596), // approximate 600.58
621            (1, 5, 1, 136),  // approximate 148.4
622            (1, 5, 2, 11),   // approximate 12.18
623            (2, 5, 2, 23),   // approximate 24.36
624            (1, 50000000, 2225652, 5709098764),
625            (1, 380928, BLOB_GASPRICE_UPDATE_FRACTION.try_into().unwrap(), 1),
626            (0, 89, 1, 0),
627            (1, 299453931, 5007716, 93359993185840258978230108),
628            (1, 88, 1, 165162653699637111792770913913821835905),
629        ] {
630            let actual = fake_exponential(factor as u128, numerator as u128, denominator as u128);
631            assert_eq!(actual, expected, "test: {t:?}");
632        }
633    }
634
635    #[test]
636    #[cfg(feature = "serde")]
637    fn serde_heap_blob() {
638        let blob = HeapBlob::repeat_byte(0x42);
639        let serialized = serde_json::to_string(&blob).unwrap();
640
641        let deserialized: HeapBlob = serde_json::from_str(&serialized).unwrap();
642        assert_eq!(blob, deserialized);
643    }
644
645    #[test]
646    fn fake_exp_saturates_when_result_overflows_u128() {
647        assert_eq!(fake_exponential(100, 88, 1), u128::MAX);
648        assert_eq!(fake_exponential(1, u64::MAX as u128, 5007716), u128::MAX);
649    }
650
651    #[test]
652    fn fake_exp_no_spurious_saturation_for_large_factor() {
653        // `factor * denominator` exceeds u128 here, but the result is exactly `factor` since
654        // e^0 == 1; the widened intermediate math must not saturate
655        assert_eq!(
656            fake_exponential(u128::MAX - 1, 0, BLOB_GASPRICE_UPDATE_FRACTION),
657            u128::MAX - 1
658        );
659    }
660
661    #[test]
662    fn osaka_excess_blob_gas_handles_large_blob_fee_comparison() {
663        let params = crate::eip7840::BlobParams::osaka();
664        let excess_blob_gas = crate::eip7691::BLOB_GASPRICE_UPDATE_FRACTION_PECTRA as u64 * 88;
665
666        let actual = params.next_block_excess_blob_gas_osaka(excess_blob_gas, 0, 1);
667
668        assert_eq!(actual, excess_blob_gas - params.target_blob_gas_per_block());
669    }
670}