Skip to content
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

fix(oracle): copy ethers code while PR is to be open and merged #369

Merged
merged 1 commit into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/common/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@ impl<P: ProviderLike> 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 @@ -234,3 +240,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 src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod grpc;
pub mod handle;
pub mod math;
pub mod mempool;
pub mod polygon;
pub mod precheck;
pub mod protos;
pub mod retry;
Expand Down
169 changes: 169 additions & 0 deletions 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();
}
}
Loading