Skip to content

Commit

Permalink
fix(oracle): copy ethers code while PR is to be open and merged
Browse files Browse the repository at this point in the history
  • Loading branch information
0xfourzerofour committed Sep 15, 2023
1 parent 4544f4c commit da5400d
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 2 deletions.
27 changes: 25 additions & 2 deletions crates/rundler/src/common/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ use std::sync::Arc;

use anyhow::Context;
use ethers::{
prelude::gas_oracle::{GasCategory, GasOracle, Polygon},
prelude::gas_oracle::{GasCategory, GasOracle},
types::{Address, Chain, U256},
};
use rundler_provider::Provider;
use rundler_types::{GasFees, UserOperation};
use rundler_utils::math;
use tokio::try_join;

use super::types::{ARBITRUM_CHAIN_IDS, OP_BEDROCK_CHAIN_IDS, POLYGON_CHAIN_IDS};
use super::{
polygon::Polygon,
types::{ARBITRUM_CHAIN_IDS, OP_BEDROCK_CHAIN_IDS, POLYGON_CHAIN_IDS},
};

/// Gas overheads for user operations
/// used in calculating the pre-verification gas
Expand Down Expand Up @@ -247,6 +250,12 @@ impl<P: Provider> FeeEstimator<P> {
}
}

const GWEI_TO_WEI: u64 = 1_000_000_000;

pub fn from_gwei_f64(gwei: f64) -> U256 {
U256::from((gwei * GWEI_TO_WEI as f64).ceil() as u64)
}

const NON_EIP_1559_CHAIN_IDS: &[u64] = &[
Chain::Arbitrum as u64,
Chain::ArbitrumNova as u64,
Expand All @@ -256,3 +265,17 @@ const NON_EIP_1559_CHAIN_IDS: &[u64] = &[
fn is_known_non_eip_1559_chain(chain_id: u64) -> bool {
NON_EIP_1559_CHAIN_IDS.contains(&chain_id)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_gwei_conversion() {
let max_priority_fee: f64 = 1.8963421368;

let result = from_gwei_f64(max_priority_fee);

assert_eq!(result, U256::from(1896342137));
}
}
1 change: 1 addition & 0 deletions crates/rundler/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod gas;
pub mod grpc;
pub mod handle;
pub mod mempool;
pub mod polygon;
pub mod precheck;
#[allow(non_snake_case)]
pub mod protos;
Expand Down
169 changes: 169 additions & 0 deletions crates/rundler/src/common/polygon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// This code was taken from ethers-rs, we will remove it once our PR is merged for rounding errors

use ethers::{
prelude::gas_oracle::{GasCategory, GasOracle, GasOracleError, Result},
types::{Chain, U256},
};
use reqwest::Client;
use serde::Deserialize;
use tonic::async_trait;
use url::Url;

use super::gas::from_gwei_f64;

const MAINNET_URL: &str = "https://gasstation.polygon.technology/v2";
const MUMBAI_URL: &str = "https://gasstation-testnet.polygon.technology/v2";

/// The [Polygon](https://docs.polygon.technology/docs/develop/tools/polygon-gas-station/) gas station API
/// Queries over HTTP and implements the `GasOracle` trait.
#[derive(Clone, Debug)]
#[must_use]
pub struct Polygon {
client: Client,
url: Url,
gas_category: GasCategory,
}

/// The response from the Polygon gas station API.
///
/// Gas prices are in __Gwei__.
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Response {
#[serde(deserialize_with = "deserialize_stringified_f64")]
pub estimated_base_fee: f64,
pub safe_low: GasEstimate,
pub standard: GasEstimate,
pub fast: GasEstimate,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GasEstimate {
#[serde(deserialize_with = "deserialize_stringified_f64")]
pub max_priority_fee: f64,
#[serde(deserialize_with = "deserialize_stringified_f64")]
pub max_fee: f64,
}

fn deserialize_stringified_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum F64OrString {
F64(serde_json::Number),
String(String),
}
match Deserialize::deserialize(deserializer)? {
F64OrString::F64(f) => f
.as_f64()
.ok_or_else(|| serde::de::Error::custom("invalid f64")),
F64OrString::String(s) => s.parse().map_err(serde::de::Error::custom),
}
}

impl Response {
#[inline]
pub fn estimate_from_category(&self, gas_category: GasCategory) -> GasEstimate {
match gas_category {
GasCategory::SafeLow => self.safe_low,
GasCategory::Standard => self.standard,
GasCategory::Fast => self.fast,
GasCategory::Fastest => self.fast,
}
}
}

impl Default for Polygon {
fn default() -> Self {
Self::new(Chain::Polygon).unwrap()
}
}

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for Polygon {
async fn fetch(&self) -> Result<U256> {
let response = self.query().await?;
let base = response.estimated_base_fee;
let prio = response
.estimate_from_category(self.gas_category)
.max_priority_fee;
let fee = base + prio;
Ok(from_gwei_f64(fee))
}

async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
let response = self.query().await?;
let estimate = response.estimate_from_category(self.gas_category);
let max = from_gwei_f64(estimate.max_fee);
let prio = from_gwei_f64(estimate.max_priority_fee);
Ok((max, prio))
}
}

impl Polygon {
pub fn new(chain: Chain) -> Result<Self> {
#[cfg(not(target_arch = "wasm32"))]
static APP_USER_AGENT: &str =
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);

let builder = Client::builder();
#[cfg(not(target_arch = "wasm32"))]
let builder = builder.user_agent(APP_USER_AGENT);

Self::with_client(builder.build()?, chain)
}

pub fn with_client(client: Client, chain: Chain) -> Result<Self> {
// TODO: Sniff chain from chain id.
let url = match chain {
Chain::Polygon => MAINNET_URL,
Chain::PolygonMumbai => MUMBAI_URL,
_ => return Err(GasOracleError::UnsupportedChain),
};
Ok(Self {
client,
url: Url::parse(url).unwrap(),
gas_category: GasCategory::Standard,
})
}

/// Sets the gas price category to be used when fetching the gas price.
pub fn category(mut self, gas_category: GasCategory) -> Self {
self.gas_category = gas_category;
self
}

/// Perform a request to the gas price API and deserialize the response.
pub async fn query(&self) -> Result<Response> {
let response = self
.client
.get(self.url.clone())
.send()
.await?
.error_for_status()?
.json()
.await?;
Ok(response)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parse_polygon_gas_station_response() {
let s = r#"{"safeLow":{"maxPriorityFee":"30.739827732","maxFee":"335.336914674"},"standard":{"maxPriorityFee":"57.257993430","maxFee":"361.855080372"},"fast":{"maxPriorityFee":"103.414268558","maxFee":"408.011355500"},"estimatedBaseFee":"304.597086942","blockTime":2,"blockNumber":43975155}"#;
let _resp: Response = serde_json::from_str(s).unwrap();
}

#[test]
fn parse_polygon_testnet_gas_station_response() {
let s = r#"{"safeLow":{"maxPriorityFee":1.3999999978,"maxFee":1.4000000157999999},"standard":{"maxPriorityFee":1.5199999980666665,"maxFee":1.5200000160666665},"fast":{"maxPriorityFee":2.0233333273333334,"maxFee":2.0233333453333335},"estimatedBaseFee":1.8e-8,"blockTime":2,"blockNumber":36917340}"#;
let _resp: Response = serde_json::from_str(s).unwrap();
}
}

0 comments on commit da5400d

Please sign in to comment.