alloy_provider/
utils.rs

1//! Provider-related utilities.
2
3use crate::{
4    fillers::{BlobGasFiller, ChainIdFiller, GasFiller, JoinFill, NonceFiller},
5    Identity,
6};
7use alloy_primitives::{U128, U64};
8use std::{fmt, fmt::Formatter};
9
10pub use alloy_eips::eip1559::Eip1559Estimation;
11
12/// The number of blocks from the past for which the fee rewards are fetched for fee estimation.
13pub const EIP1559_FEE_ESTIMATION_PAST_BLOCKS: u64 = 10;
14/// Multiplier for the current base fee to estimate max base fee for the next block.
15pub const EIP1559_BASE_FEE_MULTIPLIER: u128 = 2;
16/// The default percentile of gas premiums that are fetched for fee estimation.
17pub const EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE: f64 = 20.0;
18/// The minimum priority fee to provide.
19pub const EIP1559_MIN_PRIORITY_FEE: u128 = 1;
20
21/// An estimator function for EIP1559 fees.
22pub type EstimatorFunction = fn(u128, &[Vec<u128>]) -> Eip1559Estimation;
23
24/// A trait responsible for estimating EIP-1559 values
25pub trait Eip1559EstimatorFn: Send + Unpin {
26    /// Estimates the EIP-1559 values given the latest basefee and the recent rewards.
27    fn estimate(&self, base_fee: u128, rewards: &[Vec<u128>]) -> Eip1559Estimation;
28}
29
30/// EIP-1559 estimator variants
31#[derive(Default)]
32pub enum Eip1559Estimator {
33    /// Uses the builtin estimator
34    #[default]
35    Default,
36    /// Uses a custom estimator
37    Custom(Box<dyn Eip1559EstimatorFn>),
38}
39
40impl Eip1559Estimator {
41    /// Creates a new estimator from a closure
42    pub fn new<F>(f: F) -> Self
43    where
44        F: Fn(u128, &[Vec<u128>]) -> Eip1559Estimation + Send + Unpin + 'static,
45    {
46        Self::new_estimator(f)
47    }
48
49    /// Creates a new estimate fn
50    pub fn new_estimator<F: Eip1559EstimatorFn + 'static>(f: F) -> Self {
51        Self::Custom(Box::new(f))
52    }
53
54    /// Estimates the EIP-1559 values given the latest basefee and the recent rewards.
55    pub fn estimate(self, base_fee: u128, rewards: &[Vec<u128>]) -> Eip1559Estimation {
56        match self {
57            Self::Default => eip1559_default_estimator(base_fee, rewards),
58            Self::Custom(val) => val.estimate(base_fee, rewards),
59        }
60    }
61}
62
63impl<F> Eip1559EstimatorFn for F
64where
65    F: Fn(u128, &[Vec<u128>]) -> Eip1559Estimation + Send + Unpin,
66{
67    fn estimate(&self, base_fee: u128, rewards: &[Vec<u128>]) -> Eip1559Estimation {
68        (self)(base_fee, rewards)
69    }
70}
71
72impl fmt::Debug for Eip1559Estimator {
73    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
74        f.debug_struct("Eip1559Estimator")
75            .field(
76                "estimator",
77                &match self {
78                    Self::Default => "default",
79                    Self::Custom(_) => "custom",
80                },
81            )
82            .finish()
83    }
84}
85
86fn estimate_priority_fee(rewards: &[Vec<u128>]) -> u128 {
87    let mut rewards =
88        rewards.iter().filter_map(|r| r.first()).filter(|r| **r > 0_u128).collect::<Vec<_>>();
89    if rewards.is_empty() {
90        return EIP1559_MIN_PRIORITY_FEE;
91    }
92
93    rewards.sort_unstable();
94
95    let n = rewards.len();
96
97    let median =
98        if n % 2 == 0 { (*rewards[n / 2 - 1] + *rewards[n / 2]) / 2 } else { *rewards[n / 2] };
99
100    std::cmp::max(median, EIP1559_MIN_PRIORITY_FEE)
101}
102
103/// The default EIP-1559 fee estimator.
104///
105/// Based on the work by [MetaMask](https://github.com/MetaMask/core/blob/main/packages/gas-fee-controller/src/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.ts#L56);
106/// constants for "medium" priority level are used.
107pub fn eip1559_default_estimator(
108    base_fee_per_gas: u128,
109    rewards: &[Vec<u128>],
110) -> Eip1559Estimation {
111    let max_priority_fee_per_gas = estimate_priority_fee(rewards);
112    let potential_max_fee = base_fee_per_gas * EIP1559_BASE_FEE_MULTIPLIER;
113
114    Eip1559Estimation {
115        max_fee_per_gas: potential_max_fee + max_priority_fee_per_gas,
116        max_priority_fee_per_gas,
117    }
118}
119
120/// Convert `U128` to `u128`.
121pub(crate) fn convert_u128(r: U128) -> u128 {
122    r.to::<u128>()
123}
124
125pub(crate) fn convert_u64(r: U64) -> u64 {
126    r.to::<u64>()
127}
128
129pub(crate) fn convert_to_hashes<BlockResp: alloy_network::BlockResponse>(
130    r: Option<BlockResp>,
131) -> Option<BlockResp> {
132    r.map(|mut block| {
133        if block.transactions().is_empty() {
134            block.transactions_mut().convert_to_hashes();
135        }
136
137        block
138    })
139}
140
141/// Helper type representing the joined recommended fillers i.e [`GasFiller`],
142/// [`BlobGasFiller`], [`NonceFiller`], and [`ChainIdFiller`].
143pub type JoinedRecommendedFillers = JoinFill<
144    Identity,
145    JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
146>;
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use std::vec;
152
153    #[test]
154    fn test_estimate_priority_fee() {
155        let rewards =
156            vec![vec![10_000_000_000_u128], vec![200_000_000_000_u128], vec![3_000_000_000_u128]];
157        assert_eq!(super::estimate_priority_fee(&rewards), 10_000_000_000_u128);
158
159        let rewards = vec![
160            vec![400_000_000_000_u128],
161            vec![2_000_000_000_u128],
162            vec![5_000_000_000_u128],
163            vec![3_000_000_000_u128],
164        ];
165
166        assert_eq!(super::estimate_priority_fee(&rewards), 4_000_000_000_u128);
167
168        let rewards = vec![vec![0_u128], vec![0_u128], vec![0_u128]];
169
170        assert_eq!(super::estimate_priority_fee(&rewards), EIP1559_MIN_PRIORITY_FEE);
171
172        assert_eq!(super::estimate_priority_fee(&[]), EIP1559_MIN_PRIORITY_FEE);
173    }
174
175    #[test]
176    fn test_eip1559_default_estimator() {
177        let base_fee_per_gas = 1_000_000_000_u128;
178        let rewards = vec![
179            vec![200_000_000_000_u128],
180            vec![200_000_000_000_u128],
181            vec![300_000_000_000_u128],
182        ];
183        assert_eq!(
184            super::eip1559_default_estimator(base_fee_per_gas, &rewards),
185            Eip1559Estimation {
186                max_fee_per_gas: 202_000_000_000_u128,
187                max_priority_fee_per_gas: 200_000_000_000_u128
188            }
189        );
190
191        let base_fee_per_gas = 0u128;
192        let rewards = vec![
193            vec![200_000_000_000_u128],
194            vec![200_000_000_000_u128],
195            vec![300_000_000_000_u128],
196        ];
197
198        assert_eq!(
199            super::eip1559_default_estimator(base_fee_per_gas, &rewards),
200            Eip1559Estimation {
201                max_fee_per_gas: 200_000_000_000_u128,
202                max_priority_fee_per_gas: 200_000_000_000_u128
203            }
204        );
205    }
206}