Skip to main content

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