alloy_eips/eip1559/
helpers.rs

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