alloy-eips 2.0.5

Ethereum Improvement Proprosal (EIP) implementations
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
//! [EIP-4844] constants and helpers.
//!
//! [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844

/// Re-export the `c_kzg` crate for downstream consumers.
#[cfg(feature = "kzg")]
pub use c_kzg;

/// Module houses the KZG settings, enabling Custom and Default
#[cfg(feature = "kzg")]
pub mod env_settings;
/// This module contains functions and types used for parsing and utilizing the [Trusted Setup]( https://ceremony.ethereum.org/) for the `KzgSettings`.
#[cfg(feature = "kzg")]
pub mod trusted_setup_points;

/// Builder and utils for the [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction)
pub mod builder;
pub mod utils;

mod engine;
pub use engine::*;

/// Contains sidecar related types
#[cfg(feature = "kzg-sidecar")]
mod sidecar;
#[cfg(feature = "kzg-sidecar")]
pub use sidecar::*;

use alloy_primitives::{b256, Bytes, FixedBytes, B256, U256};

use crate::eip7840;

/// The modulus of the BLS group used in the KZG commitment scheme. All field
/// elements contained in a blob MUST be STRICTLY LESS than this value.
pub const BLS_MODULUS_BYTES: B256 =
    b256!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");

/// The modulus of the BLS group used in the KZG commitment scheme. All field
/// elements contained in a blob MUST be STRICTLY LESS than this value.
pub const BLS_MODULUS: U256 = U256::from_be_bytes(BLS_MODULUS_BYTES.0);

/// Size a single field element in bytes.
pub const FIELD_ELEMENT_BYTES: u64 = 32;

/// Size a single field element in bytes.
pub const FIELD_ELEMENT_BYTES_USIZE: usize = FIELD_ELEMENT_BYTES as usize;

/// How many field elements are stored in a single data blob.
pub const FIELD_ELEMENTS_PER_BLOB: u64 = 4096;

/// Number of usable bits in a field element. The top two bits are always zero.
pub const USABLE_BITS_PER_FIELD_ELEMENT: usize = 254;

/// The number of usable bytes in a single data blob. This is the number of
/// bytes you can encode in a blob without any field element being >=
/// [`BLS_MODULUS`].
pub const USABLE_BYTES_PER_BLOB: usize =
    USABLE_BITS_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB as usize / 8;

/// Gas consumption of a single data blob.
pub const DATA_GAS_PER_BLOB: u64 = 131_072u64; // 32*4096 = 131072 == 2^17 == 0x20000

/// How many bytes are in a blob
/// Same as [DATA_GAS_PER_BLOB], but as an usize
pub const BYTES_PER_BLOB: usize = 131_072;

/// Maximum data gas for data blobs in a single block.
pub const MAX_DATA_GAS_PER_BLOCK_DENCUN: u64 = 786_432u64; // 0xC0000 = 6 * 0x20000

/// Target data gas for data blobs in a single block.
pub const TARGET_DATA_GAS_PER_BLOCK_DENCUN: u64 = 393_216u64; // 0x60000 = 3 * 0x20000

/// Maximum number of data blobs in a single block.
pub const MAX_BLOBS_PER_BLOCK_DENCUN: usize =
    (MAX_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) as usize; // 786432 / 131072  = 6

/// Target number of data blobs in a single block.
pub const TARGET_BLOBS_PER_BLOCK_DENCUN: u64 = TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB; // 393216 / 131072 = 3

/// Determines the maximum rate of change for blob fee
pub const BLOB_GASPRICE_UPDATE_FRACTION: u128 = 3_338_477u128; // 3338477

/// Minimum gas price for a data blob
pub const BLOB_TX_MIN_BLOB_GASPRICE: u128 = 1u128;

/// Commitment version of a KZG commitment
pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;

/// How many bytes are in a commitment
pub const BYTES_PER_COMMITMENT: usize = 48;

/// How many bytes are in a proof
pub const BYTES_PER_PROOF: usize = 48;

/// A Blob serialized as 0x-prefixed hex string
pub type Blob = FixedBytes<BYTES_PER_BLOB>;

/// Helper function to deserialize boxed blobs.
#[cfg(feature = "serde")]
pub fn deserialize_blob<'de, D>(deserializer: D) -> Result<alloc::boxed::Box<Blob>, D::Error>
where
    D: serde::de::Deserializer<'de>,
{
    use serde::Deserialize;
    let raw_blob = <alloy_primitives::Bytes>::deserialize(deserializer)?;
    let blob = alloc::boxed::Box::new(
        Blob::try_from(raw_blob.as_ref()).map_err(serde::de::Error::custom)?,
    );
    Ok(blob)
}

/// Helper function to deserialize boxed blobs from a serde deserializer.
#[cfg(all(debug_assertions, feature = "serde"))]
pub fn deserialize_blobs<'de, D>(deserializer: D) -> Result<alloc::vec::Vec<Blob>, D::Error>
where
    D: serde::de::Deserializer<'de>,
{
    use alloc::vec::Vec;
    use serde::Deserialize;

    let raw_blobs = Vec::<alloy_primitives::Bytes>::deserialize(deserializer)?;
    let mut blobs = Vec::with_capacity(raw_blobs.len());
    for blob in raw_blobs {
        blobs.push(Blob::try_from(blob.as_ref()).map_err(serde::de::Error::custom)?);
    }
    Ok(blobs)
}

#[cfg(all(not(debug_assertions), feature = "serde"))]
#[inline(always)]
/// Helper function to deserialize boxed blobs from a serde deserializer.
pub fn deserialize_blobs<'de, D>(deserializer: D) -> Result<alloc::vec::Vec<Blob>, D::Error>
where
    D: serde::de::Deserializer<'de>,
{
    serde::Deserialize::deserialize(deserializer)
}

/// A heap allocated blob that serializes as 0x-prefixed hex string
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, alloy_rlp::RlpEncodableWrapper)]
pub struct HeapBlob(Bytes);

impl HeapBlob {
    /// Create a new heap blob from a byte slice.
    pub fn new(blob: &[u8]) -> Result<Self, InvalidBlobLength> {
        if blob.len() != BYTES_PER_BLOB {
            return Err(InvalidBlobLength(blob.len()));
        }

        Ok(Self(Bytes::copy_from_slice(blob)))
    }

    /// Create a new heap blob from an array.
    pub fn from_array(blob: [u8; BYTES_PER_BLOB]) -> Self {
        Self(Bytes::from(blob))
    }

    /// Create a new heap blob from [`Bytes`].
    pub fn from_bytes(bytes: Bytes) -> Result<Self, InvalidBlobLength> {
        if bytes.len() != BYTES_PER_BLOB {
            return Err(InvalidBlobLength(bytes.len()));
        }

        Ok(Self(bytes))
    }

    /// Generate a new heap blob with all bytes set to `byte`.
    pub fn repeat_byte(byte: u8) -> Self {
        Self(Bytes::from(vec![byte; BYTES_PER_BLOB]))
    }

    /// Get the inner
    pub const fn inner(&self) -> &Bytes {
        &self.0
    }
}

impl Default for HeapBlob {
    fn default() -> Self {
        Self::repeat_byte(0)
    }
}

/// Error indicating that the blob length is invalid.
#[derive(Debug, Clone)]
pub struct InvalidBlobLength(usize);
impl core::fmt::Display for InvalidBlobLength {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "Invalid blob length: {}, expected: {BYTES_PER_BLOB}", self.0)
    }
}
impl core::error::Error for InvalidBlobLength {}

#[cfg(feature = "serde")]
impl serde::Serialize for HeapBlob {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.inner().serialize(serializer)
    }
}

#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for HeapBlob {
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
        let mut blob = vec![0u8; BYTES_PER_BLOB];
        u.fill_buffer(&mut blob)?;
        Ok(Self(Bytes::from(blob)))
    }
}

#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for HeapBlob {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::de::Deserializer<'de>,
    {
        let inner = <Bytes>::deserialize(deserializer)?;

        Self::from_bytes(inner).map_err(serde::de::Error::custom)
    }
}

impl alloy_rlp::Decodable for HeapBlob {
    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
        let bytes = <Bytes>::decode(buf)?;

        Self::from_bytes(bytes).map_err(|_| alloy_rlp::Error::Custom("invalid blob length"))
    }
}

/// A commitment/proof serialized as 0x-prefixed hex string
pub type Bytes48 = FixedBytes<48>;

/// Conversion helpers for c-kzg byte wrappers.
#[cfg(feature = "kzg")]
pub trait AsCkzg: Sized {
    /// The equivalent c-kzg byte wrapper type.
    type Ckzg;

    /// Returns this value as its c-kzg equivalent.
    fn as_ckzg(&self) -> &Self::Ckzg;

    /// Returns this value as its mutable c-kzg equivalent.
    fn as_ckzg_mut(&mut self) -> &mut Self::Ckzg;

    /// Converts a c-kzg value into this type.
    fn from_ckzg(value: Self::Ckzg) -> Self;

    /// Returns this slice as its c-kzg equivalent.
    fn slice_as_ckzg(slice: &[Self]) -> &[Self::Ckzg];

    /// Returns this slice as its mutable c-kzg equivalent.
    fn slice_as_ckzg_mut(slice: &mut [Self]) -> &mut [Self::Ckzg];

    /// Converts this vector into its c-kzg equivalent.
    fn vec_as_ckzg(vec: alloc::vec::Vec<Self>) -> alloc::vec::Vec<Self::Ckzg>;

    /// Converts a c-kzg vector into this type's equivalent vector.
    fn vec_from_ckzg(vec: alloc::vec::Vec<Self::Ckzg>) -> alloc::vec::Vec<Self>;
}

/// Conversion helpers for c-kzg byte wrappers back to Alloy byte wrappers.
#[cfg(feature = "kzg")]
pub trait AsAlloy: Sized {
    /// The equivalent Alloy byte wrapper type.
    type Alloy;

    /// Returns this value as its Alloy equivalent.
    fn as_alloy(&self) -> &Self::Alloy;

    /// Returns this value as its mutable Alloy equivalent.
    fn as_alloy_mut(&mut self) -> &mut Self::Alloy;

    /// Converts this value into its Alloy equivalent.
    fn into_alloy(self) -> Self::Alloy;

    /// Returns this slice as its Alloy equivalent.
    fn slice_as_alloy(slice: &[Self]) -> &[Self::Alloy];

    /// Returns this slice as its mutable Alloy equivalent.
    fn slice_as_alloy_mut(slice: &mut [Self]) -> &mut [Self::Alloy];

    /// Converts this vector into its Alloy equivalent.
    fn vec_as_alloy(vec: alloc::vec::Vec<Self>) -> alloc::vec::Vec<Self::Alloy>;

    /// Converts this boxed slice into its Alloy equivalent.
    fn boxed_slice_as_alloy(boxed: alloc::boxed::Box<[Self]>) -> alloc::boxed::Box<[Self::Alloy]>;

    /// Converts an Alloy vector into this type's equivalent vector.
    fn vec_from_alloy(vec: alloc::vec::Vec<Self::Alloy>) -> alloc::vec::Vec<Self>;
}

#[cfg(feature = "kzg")]
macro_rules! impl_ckzg_conversions {
    ($alloy:ty, $ckzg:ty) => {
        impl AsCkzg for $alloy {
            type Ckzg = $ckzg;

            #[inline]
            fn as_ckzg(&self) -> &Self::Ckzg {
                // SAFETY: This macro is only invoked for transparent byte wrappers with the same
                // layout and alignment as their c-kzg equivalents.
                unsafe { core::mem::transmute(self) }
            }

            #[inline]
            fn as_ckzg_mut(&mut self) -> &mut Self::Ckzg {
                // SAFETY: See `AsCkzg::as_ckzg`.
                unsafe { core::mem::transmute(self) }
            }

            #[inline]
            fn from_ckzg(value: Self::Ckzg) -> Self {
                // SAFETY: See `AsCkzg::as_ckzg`.
                unsafe { core::mem::transmute(value) }
            }

            #[inline]
            fn slice_as_ckzg(slice: &[Self]) -> &[Self::Ckzg] {
                // SAFETY: See `AsCkzg::as_ckzg`.
                unsafe { core::mem::transmute(slice) }
            }

            #[inline]
            fn slice_as_ckzg_mut(slice: &mut [Self]) -> &mut [Self::Ckzg] {
                // SAFETY: See `AsCkzg::as_ckzg`.
                unsafe { core::mem::transmute(slice) }
            }

            #[inline]
            fn vec_as_ckzg(vec: alloc::vec::Vec<Self>) -> alloc::vec::Vec<Self::Ckzg> {
                // SAFETY: See `AsCkzg::as_ckzg`.
                unsafe { core::mem::transmute(vec) }
            }

            #[inline]
            fn vec_from_ckzg(vec: alloc::vec::Vec<Self::Ckzg>) -> alloc::vec::Vec<Self> {
                // SAFETY: See `AsCkzg::as_ckzg`.
                unsafe { core::mem::transmute(vec) }
            }
        }

        impl_ckzg_conversions!(reverse $alloy, $ckzg);
    };
    (reverse $alloy:ty, $ckzg:ty) => {
        impl AsAlloy for $ckzg {
            type Alloy = $alloy;

            #[inline]
            fn as_alloy(&self) -> &Self::Alloy {
                // SAFETY: This macro is only invoked for c-kzg byte wrappers with the same layout
                // and alignment as their Alloy equivalents.
                unsafe { core::mem::transmute(self) }
            }

            #[inline]
            fn as_alloy_mut(&mut self) -> &mut Self::Alloy {
                // SAFETY: See `AsAlloy::as_alloy`.
                unsafe { core::mem::transmute(self) }
            }

            #[inline]
            fn into_alloy(self) -> Self::Alloy {
                // SAFETY: See `AsAlloy::as_alloy`.
                unsafe { core::mem::transmute(self) }
            }

            #[inline]
            fn slice_as_alloy(slice: &[Self]) -> &[Self::Alloy] {
                // SAFETY: See `AsAlloy::as_alloy`.
                unsafe { core::mem::transmute(slice) }
            }

            #[inline]
            fn slice_as_alloy_mut(slice: &mut [Self]) -> &mut [Self::Alloy] {
                // SAFETY: See `AsAlloy::as_alloy`.
                unsafe { core::mem::transmute(slice) }
            }

            #[inline]
            fn vec_as_alloy(vec: alloc::vec::Vec<Self>) -> alloc::vec::Vec<Self::Alloy> {
                // SAFETY: See `AsAlloy::as_alloy`.
                unsafe { core::mem::transmute(vec) }
            }

            #[inline]
            fn boxed_slice_as_alloy(
                boxed: alloc::boxed::Box<[Self]>,
            ) -> alloc::boxed::Box<[Self::Alloy]> {
                // SAFETY: See `AsAlloy::as_alloy`.
                unsafe {
                    core::mem::transmute::<
                        alloc::boxed::Box<[Self]>,
                        alloc::boxed::Box<[Self::Alloy]>,
                    >(boxed)
                }
            }

            #[inline]
            fn vec_from_alloy(vec: alloc::vec::Vec<Self::Alloy>) -> alloc::vec::Vec<Self> {
                // SAFETY: See `AsAlloy::as_alloy`.
                unsafe { core::mem::transmute(vec) }
            }
        }
    };
}

#[cfg(feature = "kzg")]
impl_ckzg_conversions!(Blob, c_kzg::Blob);
#[cfg(feature = "kzg")]
impl_ckzg_conversions!(Bytes48, c_kzg::Bytes48);
#[cfg(feature = "kzg")]
impl_ckzg_conversions!(reverse Bytes48, c_kzg::KzgProof);
#[cfg(feature = "kzg")]
impl_ckzg_conversions!(reverse crate::eip7594::Cell, c_kzg::Cell);

/// Returns blobs as c-kzg blobs.
#[cfg(feature = "kzg")]
#[deprecated(note = "use `Blob::slice_as_ckzg` via the `AsCkzg` trait instead")]
#[inline]
pub fn blobs_as_ckzg(blobs: &[Blob]) -> &[c_kzg::Blob] {
    Blob::slice_as_ckzg(blobs)
}

/// Returns commitment/proof bytes as c-kzg bytes.
#[cfg(feature = "kzg")]
#[deprecated(note = "use `Bytes48::slice_as_ckzg` via the `AsCkzg` trait instead")]
#[inline]
pub fn bytes48_as_ckzg(bytes: &[Bytes48]) -> &[c_kzg::Bytes48] {
    Bytes48::slice_as_ckzg(bytes)
}

/// Converts c-kzg bytes into the Alloy 48-byte wrapper.
#[cfg(feature = "kzg")]
#[deprecated(note = "use `Bytes48::from_ckzg` via the `AsCkzg` trait instead")]
#[inline]
pub fn bytes48_from_ckzg(bytes: c_kzg::Bytes48) -> Bytes48 {
    Bytes48::from_ckzg(bytes)
}

/// Calculates the versioned hash for a KzgCommitment of 48 bytes.
///
/// Specified in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension)
///
/// # Panics
///
/// If the given commitment is not 48 bytes long.
#[cfg(feature = "sha2")]
pub fn kzg_to_versioned_hash(commitment: &[u8]) -> B256 {
    use sha2::Digest;

    debug_assert_eq!(commitment.len(), 48, "commitment length is not 48");
    let mut res = sha2::Sha256::digest(commitment);
    res[0] = VERSIONED_HASH_VERSION_KZG;
    B256::new(res.into())
}

/// Calculates the `excess_blob_gas` from the parent header's `blob_gas_used` and `excess_blob_gas`.
///
/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
/// (`calc_excess_blob_gas`).
#[inline]
pub const fn calc_excess_blob_gas(parent_excess_blob_gas: u64, parent_blob_gas_used: u64) -> u64 {
    eip7840::BlobParams::cancun().next_block_excess_blob_gas_osaka(
        parent_excess_blob_gas,
        parent_blob_gas_used,
        // base fee is not used in EIP-4844 excess blob gas calculation
        0,
    )
}

/// Calculates the blob gas price from the header's excess blob gas field.
///
/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
/// (`get_blob_gasprice`).
#[inline]
pub const fn calc_blob_gasprice(excess_blob_gas: u64) -> u128 {
    eip7840::BlobParams::cancun().calc_blob_fee(excess_blob_gas)
}

/// Approximates `factor * e ** (numerator / denominator)` using Taylor expansion.
///
/// This is used to calculate the blob price.
///
/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
/// (`fake_exponential`).
///
/// # Panics
///
/// This function panics if `denominator` is zero.
#[inline]
pub const fn fake_exponential(factor: u128, numerator: u128, denominator: u128) -> u128 {
    assert!(denominator != 0, "attempt to divide by zero");

    let mut i = 1;
    let mut output = 0;
    let mut numerator_accum = factor * denominator;
    while numerator_accum > 0 {
        output += numerator_accum;

        // Use checked multiplication to prevent overflow
        let Some(val) = numerator_accum.checked_mul(numerator) else {
            break;
        };

        // Denominator is asserted as not zero at the start of the function.
        numerator_accum = val / (denominator * i);
        i += 1;
    }
    output / denominator
}

#[cfg(test)]
mod tests {
    use super::*;

    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L27
    #[test]
    fn test_calc_excess_blob_gas() {
        for t @ &(excess, blobs, expected) in &[
            // The excess blob gas should not increase from zero if the used blob
            // slots are below - or equal - to the target.
            (0, 0, 0),
            (0, 1, 0),
            (0, TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB, 0),
            // If the target blob gas is exceeded, the excessBlobGas should increase
            // by however much it was overshot
            (0, (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) + 1, DATA_GAS_PER_BLOB),
            (1, (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) + 1, DATA_GAS_PER_BLOB + 1),
            (
                1,
                (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) + 2,
                2 * DATA_GAS_PER_BLOB + 1,
            ),
            // The excess blob gas should decrease by however much the target was
            // under-shot, capped at zero.
            (
                TARGET_DATA_GAS_PER_BLOCK_DENCUN,
                TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB,
                TARGET_DATA_GAS_PER_BLOCK_DENCUN,
            ),
            (
                TARGET_DATA_GAS_PER_BLOCK_DENCUN,
                (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) - 1,
                TARGET_DATA_GAS_PER_BLOCK_DENCUN - DATA_GAS_PER_BLOB,
            ),
            (
                TARGET_DATA_GAS_PER_BLOCK_DENCUN,
                (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) - 2,
                TARGET_DATA_GAS_PER_BLOCK_DENCUN - (2 * DATA_GAS_PER_BLOB),
            ),
            (DATA_GAS_PER_BLOB - 1, (TARGET_DATA_GAS_PER_BLOCK_DENCUN / DATA_GAS_PER_BLOB) - 1, 0),
        ] {
            let actual = calc_excess_blob_gas(excess, blobs * DATA_GAS_PER_BLOB);
            assert_eq!(actual, expected, "test: {t:?}");
        }
    }

    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L60
    #[test]
    fn test_calc_blob_fee() {
        let blob_fee_vectors = &[
            (0, 1),
            (2314057, 1),
            (2314058, 2),
            (10 * 1024 * 1024, 23),
            // calc_blob_gasprice approximates `e ** (excess_blob_gas /
            // BLOB_GASPRICE_UPDATE_FRACTION)` using Taylor expansion
            //
            // to roughly find where boundaries will be hit:
            // 2 ** bits = e ** (excess_blob_gas / BLOB_GASPRICE_UPDATE_FRACTION)
            // excess_blob_gas = ln(2 ** bits) * BLOB_GASPRICE_UPDATE_FRACTION
            (148099578, 18446739238971471609), // output is just below the overflow
            (148099579, 18446744762204311910), // output is just after the overflow
            (161087488, 902580055246494526580),
        ];

        for &(excess, expected) in blob_fee_vectors {
            let actual = calc_blob_gasprice(excess);
            assert_eq!(actual, expected, "test: {excess}");
        }
    }

    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L78
    #[test]
    fn fake_exp() {
        for t @ &(factor, numerator, denominator, expected) in &[
            (1u64, 0u64, 1u64, 1u128),
            (38493, 0, 1000, 38493),
            (0, 1234, 2345, 0),
            (1, 2, 1, 6), // approximate 7.389
            (1, 4, 2, 6),
            (1, 3, 1, 16), // approximate 20.09
            (1, 6, 2, 18),
            (1, 4, 1, 49), // approximate 54.60
            (1, 8, 2, 50),
            (10, 8, 2, 542), // approximate 540.598
            (11, 8, 2, 596), // approximate 600.58
            (1, 5, 1, 136),  // approximate 148.4
            (1, 5, 2, 11),   // approximate 12.18
            (2, 5, 2, 23),   // approximate 24.36
            (1, 50000000, 2225652, 5709098764),
            (1, 380928, BLOB_GASPRICE_UPDATE_FRACTION.try_into().unwrap(), 1),
        ] {
            let actual = fake_exponential(factor as u128, numerator as u128, denominator as u128);
            assert_eq!(actual, expected, "test: {t:?}");
        }
    }

    #[test]
    #[cfg(feature = "serde")]
    fn serde_heap_blob() {
        let blob = HeapBlob::repeat_byte(0x42);
        let serialized = serde_json::to_string(&blob).unwrap();

        let deserialized: HeapBlob = serde_json::from_str(&serialized).unwrap();
        assert_eq!(blob, deserialized);
    }

    #[test]
    fn fake_exp_handles_overflow() {
        // Test with very large excess blob gas values that would cause overflow
        let factor = 1u128; // BLOB_TX_MIN_BLOB_GASPRICE
        let numerator = u64::MAX as u128; // Very large excess blob gas
        let denominator = 5007716u128; // BLOB_GASPRICE_UPDATE_FRACTION_PECTRA

        // This should not panic even with very large inputs
        let result = fake_exponential(factor, numerator, denominator);

        // The result should be a valid value (not panic)
        assert!(result > 0);

        // Test with Prague parameters
        let prague_params = crate::eip7840::BlobParams::prague();
        // This should also not panic when excess_blob_gas is very large
        let _blob_fee = prague_params.calc_blob_fee(u64::MAX);
    }
}