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::{
11    eip4844::{GAS_PER_BLOB, MIN_BLOB_GASPRICE},
12    eip7918,
13};
14
15/// Structure holding block blob excess gas and it calculates blob fee
16///
17/// Incorporated as part of the Cancun upgrade via [EIP-4844].
18///
19/// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
20#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct BlobExcessGasAndPrice {
23    /// The excess blob gas of the block
24    pub excess_blob_gas: u64,
25    /// The calculated blob gas price based on the `excess_blob_gas`
26    ///
27    /// See [calc_blob_gasprice]
28    pub blob_gasprice: u128,
29}
30
31impl BlobExcessGasAndPrice {
32    /// Creates a new instance by calculating the blob gas price with [`calc_blob_gasprice`].
33    ///
34    /// `excess_blob_gas` is the excess blob gas of the block, it can be calculated with [`calc_excess_blob_gas`].
35    pub fn new(excess_blob_gas: u64, blob_base_fee_update_fraction: u64) -> Self {
36        let blob_gasprice = calc_blob_gasprice(excess_blob_gas, blob_base_fee_update_fraction);
37        Self {
38            excess_blob_gas,
39            blob_gasprice,
40        }
41    }
42
43    /// Calculate this block excess gas and price from the parent excess gas and gas used
44    /// and the target blob gas per block.
45    ///
46    /// This fields will be used to calculate `excess_blob_gas` with [`calc_excess_blob_gas`] func.
47    #[deprecated(
48        note = "Use `calc_excess_blob_gas` and `BlobExcessGasAndPrice::new` instead. Only works for forks before Osaka."
49    )]
50    pub fn from_parent_and_target(
51        parent_excess_blob_gas: u64,
52        parent_blob_gas_used: u64,
53        parent_target_blob_gas_per_block: u64,
54        blob_base_fee_update_fraction: u64,
55    ) -> Self {
56        Self::new(
57            calc_excess_blob_gas(
58                parent_excess_blob_gas,
59                parent_blob_gas_used,
60                parent_target_blob_gas_per_block,
61            ),
62            blob_base_fee_update_fraction,
63        )
64    }
65}
66
67/// Calculates the `excess_blob_gas` from the parent header's `blob_gas_used` and `excess_blob_gas`.
68/// uses [`calc_excess_blob_gas`] internally.
69#[inline]
70pub fn calc_excess_blob_gas(
71    parent_excess_blob_gas: u64,
72    parent_blob_gas_used: u64,
73    parent_target_blob_gas_per_block: u64,
74) -> u64 {
75    calc_excess_blob_gas_osaka(
76        parent_excess_blob_gas,
77        parent_blob_gas_used,
78        parent_target_blob_gas_per_block,
79        false,
80        0,
81        0,
82        0,
83        0,
84        0,
85    )
86}
87
88/// Calculates the `excess_blob_gas` from the parent header's `blob_gas_used` and `excess_blob_gas`.
89///
90/// See also [the EIP-4844 helpers]<https://eips.ethereum.org/EIPS/eip-4844#helpers>
91/// (`calc_excess_blob_gas`).
92///
93/// [EIP-7918: Blob base fee bounded by execution cost](https://eips.ethereum.org/EIPS/eip-7918)
94///
95/// `blob_base_cost` is introduced in EIP-7918 in Osaka fork. All fields after is_osaka input are not needed before Osaka.
96#[allow(clippy::too_many_arguments)]
97#[inline]
98pub fn calc_excess_blob_gas_osaka(
99    parent_excess_blob_gas: u64,
100    parent_blob_gas_used: u64,
101    parent_target_blob_gas_per_block: u64,
102    is_osaka: bool,
103    parent_base_fee_per_gas: u64,
104    parent_blob_base_fee_per_gas: u64,
105    parent_blob_base_fee_update_fraction: u64,
106    max_blob_count: u64,
107    target_blob_count: u64,
108) -> u64 {
109    let excess_and_used = parent_excess_blob_gas.saturating_add(parent_blob_gas_used);
110
111    if is_osaka {
112        if excess_and_used < parent_target_blob_gas_per_block {
113            return 0;
114        }
115
116        if (eip7918::BLOB_BASE_COST.saturating_mul(parent_base_fee_per_gas) as u128)
117            > (GAS_PER_BLOB as u128).saturating_mul(get_base_fee_per_blob_gas(
118                parent_blob_base_fee_per_gas,
119                parent_blob_base_fee_update_fraction,
120            ))
121        {
122            return excess_and_used.saturating_add(
123                parent_blob_gas_used.saturating_mul(max_blob_count - target_blob_count)
124                    / max_blob_count,
125            );
126        }
127    }
128
129    excess_and_used.saturating_sub(parent_target_blob_gas_per_block)
130}
131
132/// Calculates the blob gas price from the header's excess blob gas field.
133///
134/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
135/// (`get_blob_gasprice`).
136#[inline]
137pub fn calc_blob_gasprice(excess_blob_gas: u64, blob_base_fee_update_fraction: u64) -> u128 {
138    fake_exponential(
139        MIN_BLOB_GASPRICE,
140        excess_blob_gas,
141        blob_base_fee_update_fraction,
142    )
143}
144
145/// Calculates the base fee per blob gas. Calls [`calc_blob_gasprice`] internally.
146/// Name of the function is aligned with EIP-4844 spec.
147pub fn get_base_fee_per_blob_gas(excess_blob_gas: u64, blob_base_fee_update_fraction: u64) -> u128 {
148    calc_blob_gasprice(excess_blob_gas, blob_base_fee_update_fraction)
149}
150
151/// Approximates `factor * e ** (numerator / denominator)` using Taylor expansion.
152///
153/// This is used to calculate the blob price.
154///
155/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
156/// (`fake_exponential`).
157///
158/// # Panics
159///
160/// This function panics if `denominator` is zero.
161#[inline]
162pub fn fake_exponential(factor: u64, numerator: u64, denominator: u64) -> u128 {
163    assert_ne!(denominator, 0, "attempt to divide by zero");
164    let factor = factor as u128;
165    let numerator = numerator as u128;
166    let denominator = denominator as u128;
167
168    let mut i = 1;
169    let mut output = 0;
170    let mut numerator_accum = factor * denominator;
171    while numerator_accum > 0 {
172        output += numerator_accum;
173
174        // Denominator is asserted as not zero at the start of the function.
175        numerator_accum = (numerator_accum * numerator) / (denominator * i);
176        i += 1;
177    }
178    output / denominator
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use primitives::eip4844::{
185        self, BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, GAS_PER_BLOB,
186        TARGET_BLOB_GAS_PER_BLOCK_CANCUN as TARGET_BLOB_GAS_PER_BLOCK,
187    };
188
189    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L27
190    #[test]
191    fn test_calc_excess_blob_gas() {
192        for t @ &(excess, blobs, expected) in &[
193            // The excess blob gas should not increase from zero if the used blob
194            // slots are below - or equal - to the target.
195            (0, 0, 0),
196            (0, 1, 0),
197            (0, TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB, 0),
198            // If the target blob gas is exceeded, the excessBlobGas should increase
199            // by however much it was overshot
200            (
201                0,
202                (TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB) + 1,
203                GAS_PER_BLOB,
204            ),
205            (
206                1,
207                (TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB) + 1,
208                GAS_PER_BLOB + 1,
209            ),
210            (
211                1,
212                (TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB) + 2,
213                2 * GAS_PER_BLOB + 1,
214            ),
215            // The excess blob gas should decrease by however much the target was
216            // under-shot, capped at zero.
217            (
218                TARGET_BLOB_GAS_PER_BLOCK,
219                TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB,
220                TARGET_BLOB_GAS_PER_BLOCK,
221            ),
222            (
223                TARGET_BLOB_GAS_PER_BLOCK,
224                (TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB) - 1,
225                TARGET_BLOB_GAS_PER_BLOCK - GAS_PER_BLOB,
226            ),
227            (
228                TARGET_BLOB_GAS_PER_BLOCK,
229                (TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB) - 2,
230                TARGET_BLOB_GAS_PER_BLOCK - (2 * GAS_PER_BLOB),
231            ),
232            (
233                GAS_PER_BLOB - 1,
234                (TARGET_BLOB_GAS_PER_BLOCK / GAS_PER_BLOB) - 1,
235                0,
236            ),
237        ] {
238            let actual = calc_excess_blob_gas(
239                excess,
240                blobs * GAS_PER_BLOB,
241                eip4844::TARGET_BLOB_GAS_PER_BLOCK_CANCUN,
242            );
243            assert_eq!(actual, expected, "test: {t:?}");
244        }
245    }
246
247    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L60
248    #[test]
249    fn test_calc_blob_fee_cancun() {
250        let blob_fee_vectors = &[
251            (0, 1),
252            (2314057, 1),
253            (2314058, 2),
254            (10 * 1024 * 1024, 23),
255            // `calc_blob_gasprice` approximates `e ** (excess_blob_gas / BLOB_BASE_FEE_UPDATE_FRACTION)` using Taylor expansion
256            //
257            // to roughly find where boundaries will be hit:
258            // 2 ** bits = e ** (excess_blob_gas / BLOB_BASE_FEE_UPDATE_FRACTION)
259            // excess_blob_gas = ln(2 ** bits) * BLOB_BASE_FEE_UPDATE_FRACTION
260            (148099578, 18446739238971471609), // output is just below the overflow
261            (148099579, 18446744762204311910), // output is just after the overflow
262            (161087488, 902580055246494526580),
263        ];
264
265        for &(excess, expected) in blob_fee_vectors {
266            let actual = calc_blob_gasprice(excess, BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN);
267            assert_eq!(actual, expected, "test: {excess}");
268        }
269    }
270
271    // https://github.com/ethereum/go-ethereum/blob/28857080d732857030eda80c69b9ba2c8926f221/consensus/misc/eip4844/eip4844_test.go#L78
272    #[test]
273    fn fake_exp() {
274        for t @ &(factor, numerator, denominator, expected) in &[
275            (1u64, 0u64, 1u64, 1u128),
276            (38493, 0, 1000, 38493),
277            (0, 1234, 2345, 0),
278            (1, 2, 1, 6), // approximate 7.389
279            (1, 4, 2, 6),
280            (1, 3, 1, 16), // approximate 20.09
281            (1, 6, 2, 18),
282            (1, 4, 1, 49), // approximate 54.60
283            (1, 8, 2, 50),
284            (10, 8, 2, 542), // approximate 540.598
285            (11, 8, 2, 596), // approximate 600.58
286            (1, 5, 1, 136),  // approximate 148.4
287            (1, 5, 2, 11),   // approximate 12.18
288            (2, 5, 2, 23),   // approximate 24.36
289            (1, 50000000, 2225652, 5709098764),
290            (1, 380928, BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, 1),
291        ] {
292            let actual = fake_exponential(factor, numerator, denominator);
293            assert_eq!(actual, expected, "test: {t:?}");
294        }
295    }
296}