alloy_provider/
utils.rs

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