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}