alloy_eips/eip1559/
helpers.rs

1use crate::eip1559::{constants::GAS_LIMIT_BOUND_DIVISOR, BaseFeeParams};
2
3/// Calculates the effective gas price for a dynamic fee transaction.
4///
5/// This is a utility function for EIP-1559 and similar transactions that use dynamic fees.
6///
7/// For EIP-1559 transactions, the effective gas price is calculated as:
8/// - If no base fee: returns `max_fee_per_gas`
9/// - If base fee exists: returns `min(max_fee_per_gas, max_priority_fee_per_gas + base_fee)`
10///
11/// This ensures that the total fee doesn't exceed the maximum fee per gas, while also
12/// ensuring that the priority fee doesn't exceed the maximum priority fee per gas.
13#[inline]
14pub fn calc_effective_gas_price(
15    max_fee_per_gas: u128,
16    max_priority_fee_per_gas: u128,
17    base_fee: Option<u64>,
18) -> u128 {
19    base_fee.map_or(max_fee_per_gas, |base_fee| {
20        // if the tip is greater than the max priority fee per gas, set it to the max
21        // priority fee per gas + base fee
22        let tip = max_fee_per_gas.saturating_sub(base_fee as u128);
23        if tip > max_priority_fee_per_gas {
24            max_priority_fee_per_gas + base_fee as u128
25        } else {
26            // otherwise return the max fee per gas
27            max_fee_per_gas
28        }
29    })
30}
31
32/// Return type of EIP1155 gas fee estimator.
33///
34/// Contains EIP-1559 fields
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
38pub struct Eip1559Estimation {
39    /// The max fee per gas.
40    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
41    pub max_fee_per_gas: u128,
42    /// The max priority fee per gas.
43    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
44    pub max_priority_fee_per_gas: u128,
45}
46
47impl Eip1559Estimation {
48    /// Scales the [`Eip1559Estimation`] by the given percentage value.
49    ///
50    /// ```
51    /// use alloy_eips::eip1559::Eip1559Estimation;
52    /// let est =
53    ///     Eip1559Estimation { max_fee_per_gas: 100, max_priority_fee_per_gas: 100 }.scaled_by_pct(10);
54    /// assert_eq!(est.max_fee_per_gas, 110);
55    /// assert_eq!(est.max_priority_fee_per_gas, 110);
56    /// ```
57    pub const fn scale_by_pct(&mut self, pct: u64) {
58        self.max_fee_per_gas = self.max_fee_per_gas * (100 + pct as u128) / 100;
59        self.max_priority_fee_per_gas = self.max_priority_fee_per_gas * (100 + pct as u128) / 100;
60    }
61
62    /// Consumes the type and returns the scaled estimation.
63    pub const fn scaled_by_pct(mut self, pct: u64) -> Self {
64        self.scale_by_pct(pct);
65        self
66    }
67}
68
69/// Calculate the base fee for the next block based on the EIP-1559 specification.
70///
71/// This function calculates the base fee for the next block according to the rules defined in the
72/// EIP-1559. EIP-1559 introduces a new transaction pricing mechanism that includes a
73/// fixed-per-block network fee that is burned and dynamically adjusts block sizes to handles
74/// transient congestion.
75///
76/// For each block, the base fee per gas is determined by the gas used in the parent block and the
77/// target gas (the block gas limit divided by the elasticity multiplier). The algorithm increases
78/// the base fee when blocks are congested and decreases it when they are under the target gas
79/// usage. The base fee per gas is always burned.
80///
81/// Parameters:
82/// - `gas_used`: The gas used in the current block.
83/// - `gas_limit`: The gas limit of the current block.
84/// - `base_fee`: The current base fee per gas.
85/// - `base_fee_params`: Base fee parameters such as elasticity multiplier and max change
86///   denominator.
87///
88/// Returns:
89/// The calculated base fee for the next block as a `u64`.
90///
91/// For more information, refer to the [EIP-1559 spec](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md).
92pub fn calc_next_block_base_fee(
93    gas_used: u64,
94    gas_limit: u64,
95    base_fee: u64,
96    base_fee_params: BaseFeeParams,
97) -> u64 {
98    // Calculate the target gas by dividing the gas limit by the elasticity multiplier.
99    let gas_target = gas_limit / base_fee_params.elasticity_multiplier as u64;
100
101    match gas_used.cmp(&gas_target) {
102        // If the gas used in the current block is equal to the gas target, the base fee remains the
103        // same (no increase).
104        core::cmp::Ordering::Equal => base_fee,
105        // If the gas used in the current block is greater than the gas target, calculate a new
106        // increased base fee.
107        core::cmp::Ordering::Greater => {
108            // Calculate the increase in base fee based on the formula defined by EIP-1559.
109            base_fee
110                + (core::cmp::max(
111                    // Ensure a minimum increase of 1.
112                    1,
113                    base_fee as u128 * (gas_used - gas_target) as u128
114                        / (gas_target as u128 * base_fee_params.max_change_denominator),
115                ) as u64)
116        }
117        // If the gas used in the current block is less than the gas target, calculate a new
118        // decreased base fee.
119        core::cmp::Ordering::Less => {
120            // Calculate the decrease in base fee based on the formula defined by EIP-1559.
121            base_fee.saturating_sub(
122                (base_fee as u128 * (gas_target - gas_used) as u128
123                    / (gas_target as u128 * base_fee_params.max_change_denominator))
124                    as u64,
125            )
126        }
127    }
128}
129
130/// Calculate the gas limit for the next block based on parent and desired gas limits.
131/// Ref: <https://github.com/ethereum/go-ethereum/blob/88cbfab332c96edfbe99d161d9df6a40721bd786/core/block_validator.go#L166>
132pub fn calculate_block_gas_limit(parent_gas_limit: u64, desired_gas_limit: u64) -> u64 {
133    let delta = (parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR).saturating_sub(1);
134    let min_gas_limit = parent_gas_limit - delta;
135    let max_gas_limit = parent_gas_limit + delta;
136    desired_gas_limit.clamp(min_gas_limit, max_gas_limit)
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use crate::eip1559::constants::{MIN_PROTOCOL_BASE_FEE, MIN_PROTOCOL_BASE_FEE_U256};
143
144    #[test]
145    fn min_protocol_sanity() {
146        assert_eq!(MIN_PROTOCOL_BASE_FEE_U256.to::<u64>(), MIN_PROTOCOL_BASE_FEE);
147    }
148
149    #[test]
150    fn calculate_base_fee_success() {
151        let base_fee = [
152            1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0,
153            1, 2,
154        ];
155        let gas_used = [
156            10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000,
157            10000000,
158        ];
159        let gas_limit = [
160            10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000,
161            18000000, 18000000,
162        ];
163        let next_base_fee = [
164            1125000000, 1083333333, 1053571428, 1179939062, 1116028649, 918084097, 1063811730, 1,
165            2, 3,
166        ];
167
168        for i in 0..base_fee.len() {
169            assert_eq!(
170                next_base_fee[i],
171                calc_next_block_base_fee(
172                    gas_used[i],
173                    gas_limit[i],
174                    base_fee[i],
175                    BaseFeeParams::ethereum(),
176                )
177            );
178        }
179    }
180
181    #[test]
182    fn calculate_optimism_sepolia_base_fee_success() {
183        let base_fee = [
184            1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0,
185            1, 2,
186        ];
187        let gas_used = [
188            10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000,
189            10000000,
190        ];
191        let gas_limit = [
192            10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000,
193            18000000, 18000000,
194        ];
195        let next_base_fee = [
196            1100000048, 1080000000, 1065714297, 1167067046, 1128881311, 1028254188, 1098203452, 1,
197            2, 3,
198        ];
199
200        for i in 0..base_fee.len() {
201            assert_eq!(
202                next_base_fee[i],
203                calc_next_block_base_fee(
204                    gas_used[i],
205                    gas_limit[i],
206                    base_fee[i],
207                    BaseFeeParams::optimism_sepolia(),
208                )
209            );
210        }
211    }
212
213    #[test]
214    fn calculate_optimism_base_fee_success() {
215        let base_fee = [
216            1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0,
217            1, 2,
218        ];
219        let gas_used = [
220            10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000,
221            10000000,
222        ];
223        let gas_limit = [
224            10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000,
225            18000000, 18000000,
226        ];
227        let next_base_fee = [
228            1100000048, 1080000000, 1065714297, 1167067046, 1128881311, 1028254188, 1098203452, 1,
229            2, 3,
230        ];
231
232        for i in 0..base_fee.len() {
233            assert_eq!(
234                next_base_fee[i],
235                calc_next_block_base_fee(
236                    gas_used[i],
237                    gas_limit[i],
238                    base_fee[i],
239                    BaseFeeParams::optimism(),
240                )
241            );
242        }
243    }
244
245    #[test]
246    fn calculate_optimism_canyon_base_fee_success() {
247        let base_fee = [
248            1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0,
249            1, 2,
250        ];
251        let gas_used = [
252            10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000,
253            10000000,
254        ];
255        let gas_limit = [
256            10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000,
257            18000000, 18000000,
258        ];
259        let next_base_fee = [
260            1020000009, 1016000000, 1013142859, 1091550909, 1073187043, 1045042012, 1059031864, 1,
261            2, 3,
262        ];
263
264        for i in 0..base_fee.len() {
265            assert_eq!(
266                next_base_fee[i],
267                calc_next_block_base_fee(
268                    gas_used[i],
269                    gas_limit[i],
270                    base_fee[i],
271                    BaseFeeParams::optimism_canyon(),
272                )
273            );
274        }
275    }
276
277    #[test]
278    fn calculate_base_sepolia_base_fee_success() {
279        let base_fee = [
280            1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0,
281            1, 2,
282        ];
283        let gas_used = [
284            10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000,
285            10000000,
286        ];
287        let gas_limit = [
288            10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000,
289            18000000, 18000000,
290        ];
291        let next_base_fee = [
292            1180000000, 1146666666, 1122857142, 1244299375, 1189416692, 1028254188, 1144836295, 1,
293            2, 3,
294        ];
295
296        for i in 0..base_fee.len() {
297            assert_eq!(
298                next_base_fee[i],
299                calc_next_block_base_fee(
300                    gas_used[i],
301                    gas_limit[i],
302                    base_fee[i],
303                    BaseFeeParams::base_sepolia(),
304                )
305            );
306        }
307    }
308}