Skip to content

Commit

Permalink
New polygon gas oracle URLs (#9)
Browse files Browse the repository at this point in the history
Cherry picking gakonst#2479

Sadly we've deviated decently far from upstream so I had to bust out the
code scalpel to effectively cherry-pick w/o a bunch of changes. Opened
hyperlane-xyz/issues#806 as I think now is
probably the time to try to move away from our fork

## Motivation

Looks like the old URLs we were using were broken. The way we were
consuming this previously gracefully handled errors here to mean that it
should just fall back to legacy txs, so we had no issues. But when we
moved to using a GasOracleMiddleware, we lost the graceful error
handling

## Solution

See gakonst#2479

## PR Checklist

Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
  • Loading branch information
tkporter and DaniPopes authored Nov 29, 2023
1 parent 4a4901f commit 882ea60
Showing 1 changed file with 92 additions and 38 deletions.
130 changes: 92 additions & 38 deletions ethers-middleware/src/gas_oracle/polygon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use reqwest::Client;
use serde::Deserialize;
use url::Url;

const GAS_PRICE_ENDPOINT: &str = "https://gasstation-mainnet.matic.network/v2";
const MUMBAI_GAS_PRICE_ENDPOINT: &str = "https://gasstation-mumbai.matic.today/v2";
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
Expand All @@ -22,70 +22,124 @@ pub struct Polygon {
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Response {
estimated_base_fee: f64,
safe_low: GasEstimate,
standard: GasEstimate,
fast: GasEstimate,
#[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 {
max_priority_fee: f64,
max_fee: f64,
#[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,
}
}
}

#[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, GasOracleError> {
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(fee))
}

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

fn from_gwei(gwei: f64) -> U256 {
u256_from_f64_saturating(gwei * 1.0e9_f64)
}

impl Polygon {
pub fn new(chain: Chain) -> Result<Self, GasOracleError> {
Self::with_client(Client::new(), chain)
#[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, GasOracleError> {
// TODO: Sniff chain from chain id.
let url = match chain {
Chain::Polygon => Url::parse(GAS_PRICE_ENDPOINT).unwrap(),
Chain::PolygonMumbai => Url::parse(MUMBAI_GAS_PRICE_ENDPOINT).unwrap(),
Chain::Polygon => MAINNET_URL,
Chain::PolygonMumbai => MUMBAI_URL,
_ => return Err(GasOracleError::UnsupportedChain),
};
Ok(Self { client, url, gas_category: GasCategory::Standard })
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.
#[must_use]
pub fn category(mut self, gas_category: GasCategory) -> Self {
self.gas_category = gas_category;
self
}

/// Perform request to Blocknative, decode response
pub async fn request(&self) -> Result<(f64, GasEstimate), GasOracleError> {
let response: Response =
self.client.get(self.url.as_ref()).send().await?.error_for_status()?.json().await?;
let estimate = match self.gas_category {
GasCategory::SafeLow => response.safe_low,
GasCategory::Standard => response.standard,
GasCategory::Fast => response.fast,
GasCategory::Fastest => response.fast,
};
Ok((response.estimated_base_fee, estimate))
/// Perform a request to the gas price API and deserialize the response.
pub async fn query(&self) -> Result<Response, GasOracleError> {
let response =
self.client.get(self.url.clone()).send().await?.error_for_status()?.json().await?;
Ok(response)
}
}

#[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, GasOracleError> {
let (base_fee, estimate) = self.request().await?;
let fee = base_fee + estimate.max_priority_fee;
Ok(from_gwei(fee))
}
#[cfg(test)]
mod tests {
use super::*;

async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
let (_, estimate) = self.request().await?;
Ok((from_gwei(estimate.max_fee), from_gwei(estimate.max_priority_fee)))
#[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();
}
}

fn from_gwei(gwei: f64) -> U256 {
u256_from_f64_saturating(gwei * 1.0e9_f64)
#[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 882ea60

Please sign in to comment.