-
Notifications
You must be signed in to change notification settings - Fork 213
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[fortuna] Adjust gas oracle logic to eliminate ethers.rs priority fee…
… lower bound (#1596) * use gas oracle * jeez * cleanup * gr * format * fix comment * gr
- Loading branch information
Showing
6 changed files
with
190 additions
and
15 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
[package] | ||
name = "fortuna" | ||
version = "5.3.2" | ||
version = "5.3.3" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
pub(crate) mod eth_gas_oracle; | ||
pub(crate) mod ethereum; | ||
pub(crate) mod reader; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
use { | ||
axum::async_trait, | ||
ethers::{ | ||
prelude::{ | ||
gas_oracle::{ | ||
GasOracleError, | ||
Result, | ||
}, | ||
GasOracle, | ||
}, | ||
providers::Middleware, | ||
types::{ | ||
I256, | ||
U256, | ||
}, | ||
}, | ||
}; | ||
|
||
// The default fee estimation logic in ethers.rs includes some hardcoded constants that do not | ||
// work well in layer 2 networks because it lower bounds the priority fee at 3 gwei. | ||
// Unfortunately this logic is not configurable in ethers.rs. | ||
// | ||
// Thus, this file is copy-pasted from places in ethers.rs with all of the fee constants divided by 1000000. | ||
// See original logic here: | ||
// https://github.com/gakonst/ethers-rs/blob/master/ethers-providers/src/rpc/provider.rs#L452 | ||
|
||
/// The default max priority fee per gas, used in case the base fee is within a threshold. | ||
pub const EIP1559_FEE_ESTIMATION_DEFAULT_PRIORITY_FEE: u64 = 3_000; | ||
/// The threshold for base fee below which we use the default priority fee, and beyond which we | ||
/// estimate an appropriate value for priority fee. | ||
pub const EIP1559_FEE_ESTIMATION_PRIORITY_FEE_TRIGGER: u64 = 100_000; | ||
|
||
/// Thresholds at which the base fee gets a multiplier | ||
pub const SURGE_THRESHOLD_1: u64 = 40_000; | ||
pub const SURGE_THRESHOLD_2: u64 = 100_000; | ||
pub const SURGE_THRESHOLD_3: u64 = 200_000; | ||
|
||
|
||
/// The threshold max change/difference (in %) at which we will ignore the fee history values | ||
/// under it. | ||
pub const EIP1559_FEE_ESTIMATION_THRESHOLD_MAX_CHANGE: i64 = 200; | ||
|
||
|
||
/// Gas oracle from a [`Middleware`] implementation such as an | ||
/// Ethereum RPC provider. | ||
#[derive(Clone, Debug)] | ||
#[must_use] | ||
pub struct EthProviderOracle<M: Middleware> { | ||
provider: M, | ||
} | ||
|
||
impl<M: Middleware> EthProviderOracle<M> { | ||
pub fn new(provider: M) -> Self { | ||
Self { provider } | ||
} | ||
} | ||
|
||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] | ||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)] | ||
impl<M: Middleware> GasOracle for EthProviderOracle<M> | ||
where | ||
M::Error: 'static, | ||
{ | ||
async fn fetch(&self) -> Result<U256> { | ||
self.provider | ||
.get_gas_price() | ||
.await | ||
.map_err(|err| GasOracleError::ProviderError(Box::new(err))) | ||
} | ||
|
||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> { | ||
self.provider | ||
.estimate_eip1559_fees(Some(eip1559_default_estimator)) | ||
.await | ||
.map_err(|err| GasOracleError::ProviderError(Box::new(err))) | ||
} | ||
} | ||
|
||
/// The default EIP-1559 fee estimator which is based on the work by [MyCrypto](https://github.com/MyCryptoHQ/MyCrypto/blob/master/src/services/ApiService/Gas/eip1559.ts) | ||
fn eip1559_default_estimator(base_fee_per_gas: U256, rewards: Vec<Vec<U256>>) -> (U256, U256) { | ||
let max_priority_fee_per_gas = | ||
if base_fee_per_gas < U256::from(EIP1559_FEE_ESTIMATION_PRIORITY_FEE_TRIGGER) { | ||
U256::from(EIP1559_FEE_ESTIMATION_DEFAULT_PRIORITY_FEE) | ||
} else { | ||
std::cmp::max( | ||
estimate_priority_fee(rewards), | ||
U256::from(EIP1559_FEE_ESTIMATION_DEFAULT_PRIORITY_FEE), | ||
) | ||
}; | ||
let potential_max_fee = base_fee_surged(base_fee_per_gas); | ||
let max_fee_per_gas = if max_priority_fee_per_gas > potential_max_fee { | ||
max_priority_fee_per_gas + potential_max_fee | ||
} else { | ||
potential_max_fee | ||
}; | ||
(max_fee_per_gas, max_priority_fee_per_gas) | ||
} | ||
|
||
fn estimate_priority_fee(rewards: Vec<Vec<U256>>) -> U256 { | ||
let mut rewards: Vec<U256> = rewards | ||
.iter() | ||
.map(|r| r[0]) | ||
.filter(|r| *r > U256::zero()) | ||
.collect(); | ||
if rewards.is_empty() { | ||
return U256::zero(); | ||
} | ||
if rewards.len() == 1 { | ||
return rewards[0]; | ||
} | ||
// Sort the rewards as we will eventually take the median. | ||
rewards.sort(); | ||
|
||
// A copy of the same vector is created for convenience to calculate percentage change | ||
// between subsequent fee values. | ||
let mut rewards_copy = rewards.clone(); | ||
rewards_copy.rotate_left(1); | ||
|
||
let mut percentage_change: Vec<I256> = rewards | ||
.iter() | ||
.zip(rewards_copy.iter()) | ||
.map(|(a, b)| { | ||
let a = I256::try_from(*a).expect("priority fee overflow"); | ||
let b = I256::try_from(*b).expect("priority fee overflow"); | ||
((b - a) * 100) / a | ||
}) | ||
.collect(); | ||
percentage_change.pop(); | ||
|
||
// Fetch the max of the percentage change, and that element's index. | ||
let max_change = percentage_change.iter().max().unwrap(); | ||
let max_change_index = percentage_change | ||
.iter() | ||
.position(|&c| c == *max_change) | ||
.unwrap(); | ||
|
||
// If we encountered a big change in fees at a certain position, then consider only | ||
// the values >= it. | ||
let values = if *max_change >= EIP1559_FEE_ESTIMATION_THRESHOLD_MAX_CHANGE.into() | ||
&& (max_change_index >= (rewards.len() / 2)) | ||
{ | ||
rewards[max_change_index..].to_vec() | ||
} else { | ||
rewards | ||
}; | ||
|
||
// Return the median. | ||
values[values.len() / 2] | ||
} | ||
|
||
fn base_fee_surged(base_fee_per_gas: U256) -> U256 { | ||
if base_fee_per_gas <= U256::from(SURGE_THRESHOLD_1) { | ||
base_fee_per_gas * 2 | ||
} else if base_fee_per_gas <= U256::from(SURGE_THRESHOLD_2) { | ||
base_fee_per_gas * 16 / 10 | ||
} else if base_fee_per_gas <= U256::from(SURGE_THRESHOLD_3) { | ||
base_fee_per_gas * 14 / 10 | ||
} else { | ||
base_fee_per_gas * 12 / 10 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters