1use crate::eth::{
2 backend::{info::StorageInfo, notifications::NewBlockNotifications},
3 error::BlockchainError,
4};
5use alloy_consensus::Header;
6use alloy_eips::{
7 calc_next_block_base_fee, eip1559::BaseFeeParams, eip4844::MAX_DATA_GAS_PER_BLOCK,
8 eip7840::BlobParams,
9};
10use alloy_primitives::B256;
11use anvil_core::eth::transaction::TypedTransaction;
12use foundry_evm::revm::primitives::{BlobExcessGasAndPrice, SpecId};
13use futures::StreamExt;
14use parking_lot::{Mutex, RwLock};
15use std::{
16 collections::BTreeMap,
17 fmt,
18 future::Future,
19 pin::Pin,
20 sync::Arc,
21 task::{Context, Poll},
22};
23
24pub const MAX_FEE_HISTORY_CACHE_SIZE: u64 = 2048u64;
26
27pub const INITIAL_BASE_FEE: u64 = 1_000_000_000;
29
30pub const INITIAL_GAS_PRICE: u128 = 1_875_000_000;
32
33pub const BASE_FEE_CHANGE_DENOMINATOR: u128 = 8;
35
36pub const MIN_SUGGESTED_PRIORITY_FEE: u128 = 1e9 as u128;
38
39pub fn default_elasticity() -> f64 {
40 1f64 / BaseFeeParams::ethereum().elasticity_multiplier as f64
41}
42
43#[derive(Clone, Debug)]
45pub struct FeeManager {
46 spec_id: SpecId,
48 base_fee: Arc<RwLock<u64>>,
52 is_min_priority_fee_enforced: bool,
54 blob_excess_gas_and_price: Arc<RwLock<foundry_evm::revm::primitives::BlobExcessGasAndPrice>>,
58 gas_price: Arc<RwLock<u128>>,
62 elasticity: Arc<RwLock<f64>>,
63}
64
65impl FeeManager {
66 pub fn new(
67 spec_id: SpecId,
68 base_fee: u64,
69 is_min_priority_fee_enforced: bool,
70 gas_price: u128,
71 blob_excess_gas_and_price: BlobExcessGasAndPrice,
72 ) -> Self {
73 Self {
74 spec_id,
75 base_fee: Arc::new(RwLock::new(base_fee)),
76 is_min_priority_fee_enforced,
77 gas_price: Arc::new(RwLock::new(gas_price)),
78 blob_excess_gas_and_price: Arc::new(RwLock::new(blob_excess_gas_and_price)),
79 elasticity: Arc::new(RwLock::new(default_elasticity())),
80 }
81 }
82
83 pub fn elasticity(&self) -> f64 {
84 *self.elasticity.read()
85 }
86
87 pub fn is_eip1559(&self) -> bool {
89 (self.spec_id as u8) >= (SpecId::LONDON as u8)
90 }
91
92 pub fn is_eip4844(&self) -> bool {
93 (self.spec_id as u8) >= (SpecId::CANCUN as u8)
94 }
95
96 pub fn blob_gas_price(&self) -> u128 {
98 if self.is_eip4844() {
99 self.base_fee_per_blob_gas()
100 } else {
101 0
102 }
103 }
104
105 pub fn base_fee(&self) -> u64 {
106 if self.is_eip1559() {
107 *self.base_fee.read()
108 } else {
109 0
110 }
111 }
112
113 pub fn is_min_priority_fee_enforced(&self) -> bool {
114 self.is_min_priority_fee_enforced
115 }
116
117 pub fn raw_gas_price(&self) -> u128 {
119 *self.gas_price.read()
120 }
121
122 pub fn excess_blob_gas_and_price(&self) -> Option<BlobExcessGasAndPrice> {
123 if self.is_eip4844() {
124 Some(self.blob_excess_gas_and_price.read().clone())
125 } else {
126 None
127 }
128 }
129
130 pub fn base_fee_per_blob_gas(&self) -> u128 {
131 if self.is_eip4844() {
132 self.blob_excess_gas_and_price.read().blob_gasprice
133 } else {
134 0
135 }
136 }
137
138 pub fn set_gas_price(&self, price: u128) {
140 let mut gas = self.gas_price.write();
141 *gas = price;
142 }
143
144 pub fn set_base_fee(&self, fee: u64) {
146 trace!(target: "backend::fees", "updated base fee {:?}", fee);
147 let mut base = self.base_fee.write();
148 *base = fee;
149 }
150
151 pub fn set_blob_excess_gas_and_price(&self, blob_excess_gas_and_price: BlobExcessGasAndPrice) {
153 trace!(target: "backend::fees", "updated blob base fee {:?}", blob_excess_gas_and_price);
154 let mut base = self.blob_excess_gas_and_price.write();
155 *base = blob_excess_gas_and_price;
156 }
157
158 pub fn get_next_block_base_fee_per_gas(
160 &self,
161 gas_used: u128,
162 gas_limit: u128,
163 last_fee_per_gas: u64,
164 ) -> u64 {
165 if self.base_fee() == 0 {
169 return 0
170 }
171 calculate_next_block_base_fee(gas_used, gas_limit, last_fee_per_gas)
172 }
173
174 pub fn get_next_block_blob_base_fee_per_gas(&self, excess_blob_gas: u128) -> u128 {
176 alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas as u64)
177 }
178
179 pub fn get_next_block_blob_excess_gas(
182 &self,
183 blob_gas_used: u128,
184 blob_excess_gas: u128,
185 ) -> u64 {
186 alloy_eips::eip4844::calc_excess_blob_gas(blob_gas_used as u64, blob_excess_gas as u64)
187 }
188}
189
190pub fn calculate_next_block_base_fee(gas_used: u128, gas_limit: u128, base_fee: u64) -> u64 {
192 calc_next_block_base_fee(gas_used as u64, gas_limit as u64, base_fee, BaseFeeParams::ethereum())
193}
194
195pub struct FeeHistoryService {
197 new_blocks: NewBlockNotifications,
199 cache: FeeHistoryCache,
201 fee_history_limit: u64,
203 storage_info: StorageInfo,
205}
206
207impl FeeHistoryService {
208 pub fn new(
209 new_blocks: NewBlockNotifications,
210 cache: FeeHistoryCache,
211 storage_info: StorageInfo,
212 ) -> Self {
213 Self { new_blocks, cache, fee_history_limit: MAX_FEE_HISTORY_CACHE_SIZE, storage_info }
214 }
215
216 pub fn fee_history_limit(&self) -> u64 {
218 self.fee_history_limit
219 }
220
221 pub(crate) fn insert_cache_entry_for_block(&self, hash: B256, header: &Header) {
223 let (result, block_number) = self.create_cache_entry(hash, header);
224 self.insert_cache_entry(result, block_number);
225 }
226
227 fn create_cache_entry(
229 &self,
230 hash: B256,
231 header: &Header,
232 ) -> (FeeHistoryCacheItem, Option<u64>) {
233 let reward_percentiles: Vec<f64> = {
236 let mut percentile: f64 = 0.0;
237 (0..=200)
238 .map(|_| {
239 let val = percentile;
240 percentile += 0.5;
241 val
242 })
243 .collect()
244 };
245
246 let mut block_number: Option<u64> = None;
247 let base_fee = header.base_fee_per_gas.map(|g| g as u128).unwrap_or_default();
248 let excess_blob_gas = header.excess_blob_gas.map(|g| g as u128);
249 let blob_gas_used = header.blob_gas_used.map(|g| g as u128);
250 let base_fee_per_blob_gas = header.blob_fee(BlobParams::cancun());
251 let mut item = FeeHistoryCacheItem {
252 base_fee,
253 gas_used_ratio: 0f64,
254 blob_gas_used_ratio: 0f64,
255 rewards: Vec::new(),
256 excess_blob_gas,
257 base_fee_per_blob_gas,
258 blob_gas_used,
259 };
260
261 let current_block = self.storage_info.block(hash);
262 let current_receipts = self.storage_info.receipts(hash);
263
264 if let (Some(block), Some(receipts)) = (current_block, current_receipts) {
265 block_number = Some(block.header.number);
266
267 let gas_used = block.header.gas_used as f64;
268 let blob_gas_used = block.header.blob_gas_used.map(|g| g as f64);
269 item.gas_used_ratio = gas_used / block.header.gas_limit as f64;
270 item.blob_gas_used_ratio =
271 blob_gas_used.map(|g| g / MAX_DATA_GAS_PER_BLOCK as f64).unwrap_or(0 as f64);
272
273 let mut transactions: Vec<(_, _)> = receipts
275 .iter()
276 .enumerate()
277 .map(|(i, receipt)| {
278 let gas_used = receipt.cumulative_gas_used();
279 let effective_reward = match block.transactions.get(i).map(|tx| &tx.transaction)
280 {
281 Some(TypedTransaction::Legacy(t)) => {
282 t.tx().gas_price.saturating_sub(base_fee)
283 }
284 Some(TypedTransaction::EIP2930(t)) => {
285 t.tx().gas_price.saturating_sub(base_fee)
286 }
287 Some(TypedTransaction::EIP1559(t)) => t
288 .tx()
289 .max_priority_fee_per_gas
290 .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)),
291 Some(TypedTransaction::EIP4844(t)) => t
293 .tx()
294 .tx()
295 .max_priority_fee_per_gas
296 .min(t.tx().tx().max_fee_per_gas.saturating_sub(base_fee)),
297 Some(TypedTransaction::EIP7702(t)) => t
298 .tx()
299 .max_priority_fee_per_gas
300 .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)),
301 Some(TypedTransaction::Deposit(_)) => 0,
302 None => 0,
303 };
304
305 (gas_used, effective_reward)
306 })
307 .collect();
308
309 transactions.sort_by(|(_, a), (_, b)| a.cmp(b));
311
312 item.rewards = reward_percentiles
314 .into_iter()
315 .filter_map(|p| {
316 let target_gas = (p * gas_used / 100f64) as u64;
317 let mut sum_gas = 0;
318 for (gas_used, effective_reward) in transactions.iter().cloned() {
319 sum_gas += gas_used;
320 if target_gas <= sum_gas {
321 return Some(effective_reward)
322 }
323 }
324 None
325 })
326 .collect();
327 } else {
328 item.rewards = reward_percentiles.iter().map(|_| 0).collect();
329 }
330 (item, block_number)
331 }
332
333 fn insert_cache_entry(&self, item: FeeHistoryCacheItem, block_number: Option<u64>) {
334 if let Some(block_number) = block_number {
335 trace!(target: "fees", "insert new history item={:?} for {}", item, block_number);
336 let mut cache = self.cache.lock();
337 cache.insert(block_number, item);
338
339 let pop_next = block_number.saturating_sub(self.fee_history_limit);
341
342 let num_remove = (cache.len() as u64).saturating_sub(self.fee_history_limit);
343 for num in 0..num_remove {
344 let key = pop_next - num;
345 cache.remove(&key);
346 }
347 }
348 }
349}
350
351impl Future for FeeHistoryService {
353 type Output = ();
354
355 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
356 let pin = self.get_mut();
357
358 while let Poll::Ready(Some(notification)) = pin.new_blocks.poll_next_unpin(cx) {
359 pin.insert_cache_entry_for_block(notification.hash, notification.header.as_ref());
361 }
362
363 Poll::Pending
364 }
365}
366
367pub type FeeHistoryCache = Arc<Mutex<BTreeMap<u64, FeeHistoryCacheItem>>>;
368
369#[derive(Clone, Debug)]
371pub struct FeeHistoryCacheItem {
372 pub base_fee: u128,
373 pub gas_used_ratio: f64,
374 pub base_fee_per_blob_gas: Option<u128>,
375 pub blob_gas_used_ratio: f64,
376 pub excess_blob_gas: Option<u128>,
377 pub blob_gas_used: Option<u128>,
378 pub rewards: Vec<u128>,
379}
380
381#[derive(Clone, Default)]
382pub struct FeeDetails {
383 pub gas_price: Option<u128>,
384 pub max_fee_per_gas: Option<u128>,
385 pub max_priority_fee_per_gas: Option<u128>,
386 pub max_fee_per_blob_gas: Option<u128>,
387}
388
389impl FeeDetails {
390 pub fn zero() -> Self {
392 Self {
393 gas_price: Some(0),
394 max_fee_per_gas: Some(0),
395 max_priority_fee_per_gas: Some(0),
396 max_fee_per_blob_gas: None,
397 }
398 }
399
400 pub fn or_zero_fees(self) -> Self {
402 let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } =
403 self;
404
405 let no_fees = gas_price.is_none() && max_fee_per_gas.is_none();
406 let gas_price = if no_fees { Some(0) } else { gas_price };
407 let max_fee_per_gas = if no_fees { Some(0) } else { max_fee_per_gas };
408 let max_fee_per_blob_gas = if no_fees { None } else { max_fee_per_blob_gas };
409
410 Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas }
411 }
412
413 pub fn split(self) -> (Option<u128>, Option<u128>, Option<u128>, Option<u128>) {
415 let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } =
416 self;
417 (gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas)
418 }
419
420 pub fn new(
422 request_gas_price: Option<u128>,
423 request_max_fee: Option<u128>,
424 request_priority: Option<u128>,
425 max_fee_per_blob_gas: Option<u128>,
426 ) -> Result<Self, BlockchainError> {
427 match (request_gas_price, request_max_fee, request_priority, max_fee_per_blob_gas) {
428 (gas_price, None, None, None) => {
429 Ok(Self {
431 gas_price,
432 max_fee_per_gas: gas_price,
433 max_priority_fee_per_gas: gas_price,
434 max_fee_per_blob_gas: None,
435 })
436 }
437 (_, max_fee, max_priority, None) => {
438 if let Some(max_priority) = max_priority {
441 let max_fee = max_fee.unwrap_or_default();
442 if max_priority > max_fee {
443 return Err(BlockchainError::InvalidFeeInput)
444 }
445 }
446 Ok(Self {
447 gas_price: max_fee,
448 max_fee_per_gas: max_fee,
449 max_priority_fee_per_gas: max_priority,
450 max_fee_per_blob_gas: None,
451 })
452 }
453 (_, max_fee, max_priority, max_fee_per_blob_gas) => {
454 if let Some(max_priority) = max_priority {
457 let max_fee = max_fee.unwrap_or_default();
458 if max_priority > max_fee {
459 return Err(BlockchainError::InvalidFeeInput)
460 }
461 }
462 Ok(Self {
463 gas_price: max_fee,
464 max_fee_per_gas: max_fee,
465 max_priority_fee_per_gas: max_priority,
466 max_fee_per_blob_gas,
467 })
468 }
469 }
470 }
471}
472
473impl fmt::Debug for FeeDetails {
474 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
475 write!(fmt, "Fees {{ ")?;
476 write!(fmt, "gas_price: {:?}, ", self.gas_price)?;
477 write!(fmt, "max_fee_per_gas: {:?}, ", self.max_fee_per_gas)?;
478 write!(fmt, "max_priority_fee_per_gas: {:?}, ", self.max_priority_fee_per_gas)?;
479 write!(fmt, "}}")?;
480 Ok(())
481 }
482}