1use 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
19pub const EIP1559_FEE_ESTIMATION_PAST_BLOCKS: u64 = 10;
21pub const EIP1559_BASE_FEE_MULTIPLIER: u128 = 2;
23pub const EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE: f64 = 20.0;
25pub const EIP1559_MIN_PRIORITY_FEE: u128 = 1;
27
28pub type EstimatorFunction = fn(u128, &[Vec<u128>]) -> Eip1559Estimation;
30
31pub trait Eip1559EstimatorFn: Send + Sync + Unpin {
33 fn estimate(&self, base_fee: u128, rewards: &[Vec<u128>]) -> Eip1559Estimation;
35}
36
37#[derive(Default, Clone)]
39pub enum Eip1559Estimator {
40 #[default]
42 Default,
43 Custom(Arc<dyn Eip1559EstimatorFn>),
45}
46
47impl Eip1559Estimator {
48 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 pub fn new_estimator<F: Eip1559EstimatorFn + 'static>(f: F) -> Self {
58 Self::Custom(Arc::new(f))
59 }
60
61 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
110pub 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
127pub(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
148pub(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
164pub(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
181pub 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}