Skip to content

Commit

Permalink
[fortuna] Adjust gas oracle logic to eliminate ethers.rs priority fee…
Browse files Browse the repository at this point in the history
… lower bound (#1596)

* use gas oracle

* jeez

* cleanup

* gr

* format

* fix comment

* gr
  • Loading branch information
jayantk authored May 23, 2024
1 parent 5f3188a commit 98c1fcc
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 15 deletions.
2 changes: 1 addition & 1 deletion apps/fortuna/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/fortuna/Cargo.toml
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]
Expand Down
1 change: 1 addition & 0 deletions apps/fortuna/src/chain.rs
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;
161 changes: 161 additions & 0 deletions apps/fortuna/src/chain/eth_gas_oracle.rs
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
}
}
36 changes: 24 additions & 12 deletions apps/fortuna/src/chain/ethereum.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use {
crate::{
chain::reader::{
self,
BlockNumber,
BlockStatus,
EntropyReader,
RequestedWithCallbackEvent,
chain::{
eth_gas_oracle::EthProviderOracle,
reader::{
self,
BlockNumber,
BlockStatus,
EntropyReader,
RequestedWithCallbackEvent,
},
},
config::EthereumConfig,
},
Expand All @@ -24,6 +27,7 @@ use {
},
core::types::Address,
middleware::{
gas_oracle::GasOracleMiddleware,
transformer::{
Transformer,
TransformerError,
Expand Down Expand Up @@ -63,9 +67,12 @@ abigen!(
);

pub type SignablePythContract = PythRandom<
TransformerMiddleware<
NonceManagerMiddleware<SignerMiddleware<Provider<Http>, LocalWallet>>,
LegacyTxTransformer,
GasOracleMiddleware<
TransformerMiddleware<
NonceManagerMiddleware<SignerMiddleware<Provider<Http>, LocalWallet>>,
LegacyTxTransformer,
>,
EthProviderOracle<Provider<Http>>,
>,
>;
pub type PythContract = PythRandom<Provider<Http>>;
Expand Down Expand Up @@ -96,6 +103,8 @@ impl SignablePythContract {
let provider = Provider::<Http>::try_from(&chain_config.geth_rpc_addr)?;
let chain_id = provider.get_chainid().await?;

let gas_oracle = EthProviderOracle::new(provider.clone());

let transformer = LegacyTxTransformer {
use_legacy_tx: chain_config.legacy_tx,
};
Expand All @@ -108,9 +117,12 @@ impl SignablePythContract {

Ok(PythRandom::new(
chain_config.contract_addr,
Arc::new(TransformerMiddleware::new(
NonceManagerMiddleware::new(SignerMiddleware::new(provider, wallet__), address),
transformer,
Arc::new(GasOracleMiddleware::new(
TransformerMiddleware::new(
NonceManagerMiddleware::new(SignerMiddleware::new(provider, wallet__), address),
transformer,
),
gas_oracle,
)),
))
}
Expand Down
3 changes: 2 additions & 1 deletion apps/fortuna/src/keeper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ pub async fn run_keeper_threads(
.await
.expect("Chain config should be valid"),
);
let keeper_address = contract.client().inner().inner().signer().address();
let keeper_address = contract.client().inner().inner().inner().signer().address();

let fulfilled_requests_cache = Arc::new(RwLock::new(HashMap::<u64, RequestState>::new()));

Expand Down Expand Up @@ -430,6 +430,7 @@ pub async fn process_event(
.client()
.inner()
.inner()
.inner()
.signer()
.address()
.to_string(),
Expand Down

0 comments on commit 98c1fcc

Please sign in to comment.