-
Notifications
You must be signed in to change notification settings - Fork 222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(fortuna): add eip1559_fee_multiplier_pct to adjust gas fees #2191
Changes from 5 commits
5cc054e
b3aaeb8
c6a438e
a1bb2ac
632183b
ec4d078
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,44 +6,35 @@ use { | |
GasOracle, | ||
}, | ||
providers::Middleware, | ||
types::{I256, U256}, | ||
types::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; | ||
/// Configuration for GasOracle | ||
#[derive(Clone, Debug)] | ||
pub struct GasOracleConfig { | ||
pub eip1559_fee_multiplier_pct: u64, | ||
} | ||
|
||
/// Gas oracle from a [`Middleware`] implementation such as an | ||
/// Ethereum RPC provider. | ||
#[derive(Clone, Debug)] | ||
#[must_use] | ||
pub struct EthProviderOracle<M: Middleware> { | ||
provider: M, | ||
config: GasOracleConfig, | ||
} | ||
|
||
impl<M: Middleware> EthProviderOracle<M> { | ||
pub fn new(provider: M) -> Self { | ||
Self { provider } | ||
/// Creates a new EthProviderOracle with the given provider and optional fee multiplier. | ||
/// If no multiplier is provided, defaults to 100% (no change to fees). | ||
pub fn new(provider: M, eip1559_fee_multiplier_pct: Option<u64>) -> Self { | ||
Self { | ||
provider, | ||
config: GasOracleConfig { | ||
eip1559_fee_multiplier_pct: eip1559_fee_multiplier_pct.unwrap_or(100), | ||
}, | ||
} | ||
} | ||
} | ||
|
||
|
@@ -61,93 +52,15 @@ where | |
} | ||
|
||
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> { | ||
self.provider | ||
.estimate_eip1559_fees(Some(eip1559_default_estimator)) | ||
let (max_fee_per_gas, max_priority_fee_per_gas) = self | ||
.provider | ||
.estimate_eip1559_fees(None) | ||
.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) | ||
pub 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] | ||
} | ||
.map_err(|err| GasOracleError::ProviderError(Box::new(err)))?; | ||
|
||
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 | ||
// Apply the fee multiplier | ||
let multiplier = U256::from(self.config.eip1559_fee_multiplier_pct); | ||
let adjusted_max_fee = (max_fee_per_gas * multiplier) / U256::from(100); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should be adjusting max_priority_fee_per_gas, not max_fee_per_gas |
||
Ok((adjusted_max_fee, max_priority_fee_per_gas)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,6 @@ use { | |
crate::{ | ||
api::{self, BlockchainState, ChainId}, | ||
chain::{ | ||
eth_gas_oracle::eip1559_default_estimator, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please revert all changes to this file -- it should be completely unchanged in the diff |
||
ethereum::{ | ||
InstrumentedPythContract, InstrumentedSignablePythContract, PythContractCall, | ||
}, | ||
|
@@ -277,6 +276,7 @@ pub async fn run_keeper_threads( | |
chain_eth_config.target_profit_pct, | ||
chain_eth_config.max_profit_pct, | ||
chain_eth_config.fee, | ||
chain_eth_config.eip1559_fee_multiplier_pct, | ||
) | ||
.in_current_span(), | ||
); | ||
|
@@ -997,6 +997,7 @@ pub async fn adjust_fee_wrapper( | |
target_profit_pct: u64, | ||
max_profit_pct: u64, | ||
min_fee_wei: u128, | ||
eip1559_fee_multiplier_pct: u64, | ||
) { | ||
// The maximum balance of accrued fees + provider wallet balance. None if we haven't observed a value yet. | ||
let mut high_water_pnl: Option<U256> = None; | ||
|
@@ -1014,6 +1015,7 @@ pub async fn adjust_fee_wrapper( | |
min_fee_wei, | ||
&mut high_water_pnl, | ||
&mut sequence_number_of_last_fee_update, | ||
eip1559_fee_multiplier_pct, | ||
) | ||
.in_current_span() | ||
.await | ||
|
@@ -1097,6 +1099,7 @@ pub async fn adjust_fee_if_necessary( | |
min_fee_wei: u128, | ||
high_water_pnl: &mut Option<U256>, | ||
sequence_number_of_last_fee_update: &mut Option<u64>, | ||
eip1559_fee_multiplier_pct: u64, | ||
) -> Result<()> { | ||
let provider_info = contract | ||
.get_provider_info(provider_address) | ||
|
@@ -1109,9 +1112,14 @@ pub async fn adjust_fee_if_necessary( | |
} | ||
|
||
// Calculate target window for the on-chain fee. | ||
let max_callback_cost: u128 = estimate_tx_cost(contract.clone(), legacy_tx, gas_limit.into()) | ||
.await | ||
.map_err(|e| anyhow!("Could not estimate transaction cost. error {:?}", e))?; | ||
let max_callback_cost: u128 = estimate_tx_cost( | ||
contract.clone(), | ||
legacy_tx, | ||
gas_limit.into(), | ||
eip1559_fee_multiplier_pct, | ||
) | ||
.await | ||
.map_err(|e| anyhow!("Could not estimate transaction cost. error {:?}", e))?; | ||
let target_fee_min = std::cmp::max( | ||
(max_callback_cost * (100 + u128::from(min_profit_pct))) / 100, | ||
min_fee_wei, | ||
|
@@ -1197,6 +1205,7 @@ pub async fn estimate_tx_cost( | |
contract: Arc<InstrumentedSignablePythContract>, | ||
use_legacy_tx: bool, | ||
gas_used: u128, | ||
eip1559_fee_multiplier_pct: u64, | ||
) -> Result<u128> { | ||
let middleware = contract.client(); | ||
|
||
|
@@ -1208,11 +1217,13 @@ pub async fn estimate_tx_cost( | |
.try_into() | ||
.map_err(|e| anyhow!("gas price doesn't fit into 128 bits. error: {:?}", e))? | ||
} else { | ||
let (max_fee_per_gas, max_priority_fee_per_gas) = middleware | ||
.estimate_eip1559_fees(Some(eip1559_default_estimator)) | ||
.await?; | ||
let (max_fee_per_gas, max_priority_fee_per_gas) = | ||
middleware.estimate_eip1559_fees(None).await?; | ||
|
||
let total_fee = max_fee_per_gas + max_priority_fee_per_gas; | ||
let adjusted_fee = total_fee * eip1559_fee_multiplier_pct / 100; | ||
|
||
(max_fee_per_gas + max_priority_fee_per_gas) | ||
adjusted_fee | ||
.try_into() | ||
.map_err(|e| anyhow!("gas price doesn't fit into 128 bits. error: {:?}", e))? | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line should still be using eip1559_default_estimator. Please undo the deletion of that here.