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::{fmt, fmt::Formatter};
13
14pub use alloy_eips::eip1559::Eip1559Estimation;
15
16pub const EIP1559_FEE_ESTIMATION_PAST_BLOCKS: u64 = 10;
18pub const EIP1559_BASE_FEE_MULTIPLIER: u128 = 2;
20pub const EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE: f64 = 20.0;
22pub const EIP1559_MIN_PRIORITY_FEE: u128 = 1;
24
25pub type EstimatorFunction = fn(u128, &[Vec<u128>]) -> Eip1559Estimation;
27
28pub trait Eip1559EstimatorFn: Send + Unpin {
30 fn estimate(&self, base_fee: u128, rewards: &[Vec<u128>]) -> Eip1559Estimation;
32}
33
34#[derive(Default)]
36pub enum Eip1559Estimator {
37 #[default]
39 Default,
40 Custom(Box<dyn Eip1559EstimatorFn>),
42}
43
44impl Eip1559Estimator {
45 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 pub fn new_estimator<F: Eip1559EstimatorFn + 'static>(f: F) -> Self {
55 Self::Custom(Box::new(f))
56 }
57
58 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
107pub 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
124pub(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
145pub(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
161pub 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}