revm_context_interface/block/
blob.rs

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