revm_context_interface/block/
blob.rs

1//! Blob (EIP-4844) related functions and types. [`BlobExcessGasAndPrice`] is struct that helps with
2//! calculating blob gas price and excess blob gas.
3//!
4//! See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers).
5//!
6//! [`calc_blob_gasprice`] and [`calc_excess_blob_gas`] are used to calculate the blob gas price and
7//! excess blob gas.
8//!
9//! [`BlobExcessGasAndPrice`] is used to store the blob gas price and excess blob gas.s
10use primitives::eip4844::MIN_BLOB_GASPRICE;
11
12/// Structure holding block blob excess gas and it calculates blob fee
13///
14/// Incorporated as part of the Cancun upgrade via [EIP-4844].
15///
16/// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
17#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct BlobExcessGasAndPrice {
20    /// The excess blob gas of the block
21    pub excess_blob_gas: u64,
22    /// The calculated blob gas price based on the `excess_blob_gas`
23    ///
24    /// See [calc_blob_gasprice]
25    pub blob_gasprice: u128,
26}
27
28impl BlobExcessGasAndPrice {
29    /// Creates a new instance by calculating the blob gas price with [`calc_blob_gasprice`].
30    pub fn new(excess_blob_gas: u64, blob_base_fee_update_fraction: u64) -> Self {
31        let blob_gasprice = calc_blob_gasprice(excess_blob_gas, blob_base_fee_update_fraction);
32        Self {
33            excess_blob_gas,
34            blob_gasprice,
35        }
36    }
37
38    /// Calculate this block excess gas and price from the parent excess gas and gas used
39    /// and the target blob gas per block.
40    ///
41    /// This fields will be used to calculate `excess_blob_gas` with [`calc_excess_blob_gas`] func.
42    pub fn from_parent_and_target(
43        parent_excess_blob_gas: u64,
44        parent_blob_gas_used: u64,
45        parent_target_blob_gas_per_block: u64,
46        blob_base_fee_update_fraction: u64,
47    ) -> Self {
48        Self::new(
49            calc_excess_blob_gas(
50                parent_excess_blob_gas,
51                parent_blob_gas_used,
52                parent_target_blob_gas_per_block,
53            ),
54            blob_base_fee_update_fraction,
55        )
56    }
57}
58
59/// Calculates the `excess_blob_gas` from the parent header's `blob_gas_used` and `excess_blob_gas`.
60///
61/// See also [the EIP-4844 helpers]<https://eips.ethereum.org/EIPS/eip-4844#helpers>
62/// (`calc_excess_blob_gas`).
63#[inline]
64pub fn calc_excess_blob_gas(
65    parent_excess_blob_gas: u64,
66    parent_blob_gas_used: u64,
67    parent_target_blob_gas_per_block: u64,
68) -> u64 {
69    (parent_excess_blob_gas + parent_blob_gas_used).saturating_sub(parent_target_blob_gas_per_block)
70}
71
72/// Calculates the blob gas price from the header's excess blob gas field.
73///
74/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
75/// (`get_blob_gasprice`).
76#[inline]
77pub fn calc_blob_gasprice(excess_blob_gas: u64, blob_base_fee_update_fraction: u64) -> u128 {
78    fake_exponential(
79        MIN_BLOB_GASPRICE,
80        excess_blob_gas,
81        blob_base_fee_update_fraction,
82    )
83}
84
85/// Approximates `factor * e ** (numerator / denominator)` using Taylor expansion.
86///
87/// This is used to calculate the blob price.
88///
89/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
90/// (`fake_exponential`).
91///
92/// # Panics
93///
94/// This function panics if `denominator` is zero.
95#[inline]
96pub fn fake_exponential(factor: u64, numerator: u64, denominator: u64) -> u128 {
97    assert_ne!(denominator, 0, "attempt to divide by zero");
98    let factor = factor as u128;
99    let numerator = numerator as u128;
100    let denominator = denominator as u128;
101
102    let mut i = 1;
103    let mut output = 0;
104    let mut numerator_accum = factor * denominator;
105    while numerator_accum > 0 {
106        output += numerator_accum;
107
108        // Denominator is asserted as not zero at the start of the function.
109        numerator_accum = (numerator_accum * numerator) / (denominator * i);
110        i += 1;
111    }
112    output / denominator
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use primitives::eip4844::{
119        self, BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, GAS_PER_BLOB,
120        TARGET_BLOB_GAS_PER_BLOCK_CANCUN as TARGET_BLOB_GAS_PER_BLOCK,
121    };
122
123    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L27
124    #[test]
125    fn test_calc_excess_blob_gas() {
126        for t @ &(excess, blobs, expected) in &[
127            // The excess blob gas should not increase from zero if the used blob
128            // slots are below - or equal - to the target.
129            (0, 0, 0),
130            (0, 1, 0),
131            (0, TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB, 0),
132            // If the target blob gas is exceeded, the excessBlobGas should increase
133            // by however much it was overshot
134            (
135                0,
136                (TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB) + 1,
137                GAS_PER_BLOB,
138            ),
139            (
140                1,
141                (TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB) + 1,
142                GAS_PER_BLOB + 1,
143            ),
144            (
145                1,
146                (TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB) + 2,
147                2 * GAS_PER_BLOB + 1,
148            ),
149            // The excess blob gas should decrease by however much the target was
150            // under-shot, capped at zero.
151            (
152                TARGET_BLOB_GAS_PER_BLOCK,
153                TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB,
154                TARGET_BLOB_GAS_PER_BLOCK,
155            ),
156            (
157                TARGET_BLOB_GAS_PER_BLOCK,
158                (TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB) - 1,
159                TARGET_BLOB_GAS_PER_BLOCK - GAS_PER_BLOB,
160            ),
161            (
162                TARGET_BLOB_GAS_PER_BLOCK,
163                (TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB) - 2,
164                TARGET_BLOB_GAS_PER_BLOCK - (2 * GAS_PER_BLOB),
165            ),
166            (
167                GAS_PER_BLOB - 1,
168                (TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB) - 1,
169                0,
170            ),
171        ] {
172            let actual = calc_excess_blob_gas(
173                excess,
174                blobs * GAS_PER_BLOB,
175                eip4844::TARGET_BLOB_GAS_PER_BLOCK_CANCUN,
176            );
177            assert_eq!(actual, expected, "test: {t:?}");
178        }
179    }
180
181    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L60
182    #[test]
183    fn test_calc_blob_fee_cancun() {
184        let blob_fee_vectors = &[
185            (0, 1),
186            (2314057, 1),
187            (2314058, 2),
188            (10 * 1024 * 1024, 23),
189            // `calc_blob_gasprice` approximates `e ** (excess_blob_gas / BLOB_BASE_FEE_UPDATE_FRACTION)` using Taylor expansion
190            //
191            // to roughly find where boundaries will be hit:
192            // 2 ** bits = e ** (excess_blob_gas / BLOB_BASE_FEE_UPDATE_FRACTION)
193            // excess_blob_gas = ln(2 ** bits) * BLOB_BASE_FEE_UPDATE_FRACTION
194            (148099578, 18446739238971471609), // output is just below the overflow
195            (148099579, 18446744762204311910), // output is just after the overflow
196            (161087488, 902580055246494526580),
197        ];
198
199        for &(excess, expected) in blob_fee_vectors {
200            let actual = calc_blob_gasprice(excess, BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN);
201            assert_eq!(actual, expected, "test: {excess}");
202        }
203    }
204
205    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L78
206    #[test]
207    fn fake_exp() {
208        for t @ &(factor, numerator, denominator, expected) in &[
209            (1u64, 0u64, 1u64, 1u128),
210            (38493, 0, 1000, 38493),
211            (0, 1234, 2345, 0),
212            (1, 2, 1, 6), // approximate 7.389
213            (1, 4, 2, 6),
214            (1, 3, 1, 16), // approximate 20.09
215            (1, 6, 2, 18),
216            (1, 4, 1, 49), // approximate 54.60
217            (1, 8, 2, 50),
218            (10, 8, 2, 542), // approximate 540.598
219            (11, 8, 2, 596), // approximate 600.58
220            (1, 5, 1, 136),  // approximate 148.4
221            (1, 5, 2, 11),   // approximate 12.18
222            (2, 5, 2, 23),   // approximate 24.36
223            (1, 50000000, 2225652, 5709098764),
224            (1, 380928, BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, 1),
225        ] {
226            let actual = fake_exponential(factor, numerator, denominator);
227            assert_eq!(actual, expected, "test: {t:?}");
228        }
229    }
230}