From 49b4ac7acbbd71b4d10f7780c8d2efe9af27799b Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Sat, 20 Aug 2022 16:04:08 -0700 Subject: [PATCH] More gas oracles (#1251) * Deriver auto_impls for GasOracle * Provider as GasOracle * impl fill_transaction in GasOracleMiddleware --- Cargo.lock | 17 +++++++- ethers-middleware/Cargo.toml | 1 + ethers-middleware/src/gas_oracle/gas_now.rs | 6 +-- .../src/gas_oracle/middleware.rs | 41 +++++++++++-------- ethers-middleware/src/gas_oracle/mod.rs | 13 ++++++ .../src/gas_oracle/provider_oracle.rs | 40 ++++++++++++++++++ 6 files changed, 97 insertions(+), 21 deletions(-) create mode 100644 ethers-middleware/src/gas_oracle/provider_oracle.rs diff --git a/Cargo.lock b/Cargo.lock index cad1d54e..e7c28986 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,6 +135,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "auto_impl" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7862e21c893d65a1650125d157eaeec691439379a1cee17ee49031b79236ada4" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "auto_impl" version = "1.0.1" @@ -1365,6 +1377,7 @@ name = "ethers-middleware" version = "0.17.0" dependencies = [ "async-trait", + "auto_impl 0.5.0", "ethers-contract", "ethers-core", "ethers-etherscan", @@ -1393,7 +1406,7 @@ name = "ethers-providers" version = "0.17.0" dependencies = [ "async-trait", - "auto_impl", + "auto_impl 1.0.1", "base64 0.13.0", "bytes", "ethers-core", @@ -1547,7 +1560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "089263294bb1c38ac73649a6ad563dd9a5142c8dc0482be15b8b9acb22a1611e" dependencies = [ "arrayvec 0.7.2", - "auto_impl", + "auto_impl 1.0.1", "bytes", "ethereum-types", "fastrlp-derive", diff --git a/ethers-middleware/Cargo.toml b/ethers-middleware/Cargo.toml index 6a7bc461..0544e54b 100644 --- a/ethers-middleware/Cargo.toml +++ b/ethers-middleware/Cargo.toml @@ -21,6 +21,7 @@ ethers-providers = { version = "^0.17.0", path = "../ethers-providers", default- ethers-signers = { version = "^0.17.0", path = "../ethers-signers", default-features = false } async-trait = { version = "0.1.50", default-features = false } +auto_impl = { version = "0.5.0", default-features = false } serde = { version = "1.0.124", default-features = false, features = ["derive"] } thiserror = { version = "1.0", default-features = false } futures-util = { version = "^0.3" } diff --git a/ethers-middleware/src/gas_oracle/gas_now.rs b/ethers-middleware/src/gas_oracle/gas_now.rs index a1444ff1..65ddc46c 100644 --- a/ethers-middleware/src/gas_oracle/gas_now.rs +++ b/ethers-middleware/src/gas_oracle/gas_now.rs @@ -23,7 +23,7 @@ struct GasNowResponseWrapper { data: GasNowResponse, } -#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct GasNowResponse { pub rapid: u64, pub fast: u64, @@ -41,7 +41,7 @@ impl GasNow { pub fn with_client(client: Client) -> Self { let url = Url::parse(GAS_NOW_URL).expect("invalid url"); - Self { url, gas_category: GasCategory::Standard } + Self { client, url, gas_category: GasCategory::Standard } } /// Sets the gas price category to be used when fetching the gas price. @@ -64,7 +64,7 @@ impl GasNow { impl Default for GasNow { fn default() -> Self { - Self::new(Client::new()) + Self::new() } } diff --git a/ethers-middleware/src/gas_oracle/middleware.rs b/ethers-middleware/src/gas_oracle/middleware.rs index 844a5169..272b0150 100644 --- a/ethers-middleware/src/gas_oracle/middleware.rs +++ b/ethers-middleware/src/gas_oracle/middleware.rs @@ -56,24 +56,11 @@ where &self.inner } - async fn get_gas_price(&self) -> Result { - Ok(self.gas_oracle.fetch().await?) - } - - async fn estimate_eip1559_fees( + async fn fill_transaction( &self, - _: Option>) -> (U256, U256)>, - ) -> Result<(U256, U256), Self::Error> { - Ok(self.gas_oracle.estimate_eip1559_fees().await?) - } - - async fn send_transaction + Send + Sync>( - &self, - tx: T, + tx: &mut TypedTransaction, block: Option, - ) -> Result, Self::Error> { - let mut tx = tx.into(); - + ) -> Result<(), Self::Error> { match tx { TypedTransaction::Legacy(ref mut tx) => { if tx.gas_price.is_none() { @@ -98,6 +85,28 @@ where } } }; + + self.inner().fill_transaction(tx, block).await.map_err(FromErr::from) + } + + async fn get_gas_price(&self) -> Result { + Ok(self.gas_oracle.fetch().await?) + } + + async fn estimate_eip1559_fees( + &self, + _: Option>) -> (U256, U256)>, + ) -> Result<(U256, U256), Self::Error> { + Ok(self.gas_oracle.estimate_eip1559_fees().await?) + } + + async fn send_transaction + Send + Sync>( + &self, + tx: T, + block: Option, + ) -> Result, Self::Error> { + let mut tx = tx.into(); + self.fill_transaction(&mut tx, block).await?; self.inner.send_transaction(tx, block).await.map_err(MiddlewareError::MiddlewareError) } } diff --git a/ethers-middleware/src/gas_oracle/mod.rs b/ethers-middleware/src/gas_oracle/mod.rs index 155c277c..4a7a701c 100644 --- a/ethers-middleware/src/gas_oracle/mod.rs +++ b/ethers-middleware/src/gas_oracle/mod.rs @@ -22,10 +22,18 @@ pub use cache::Cache; mod polygon; pub use polygon::Polygon; +mod gas_now; +pub use gas_now::GasNow; + +mod provider_oracle; +pub use provider_oracle::ProviderOracle; + use ethers_core::types::U256; use async_trait::async_trait; +use auto_impl::auto_impl; use reqwest::Error as ReqwestError; +use std::error::Error; use thiserror::Error; const GWEI_TO_WEI: u64 = 1000000000; @@ -73,6 +81,10 @@ pub enum GasOracleError { #[error("Chain is not supported by the oracle")] UnsupportedChain, + + /// Error thrown when the provider failed. + #[error("Chain is not supported by the oracle")] + ProviderError(#[from] Box), } /// `GasOracle` is a trait that an underlying gas oracle needs to implement. @@ -95,6 +107,7 @@ pub enum GasOracleError { /// ``` #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[auto_impl(&, Box, Arc)] pub trait GasOracle: Send + Sync + std::fmt::Debug { /// Makes an asynchronous HTTP query to the underlying `GasOracle` /// diff --git a/ethers-middleware/src/gas_oracle/provider_oracle.rs b/ethers-middleware/src/gas_oracle/provider_oracle.rs new file mode 100644 index 00000000..894bb5ab --- /dev/null +++ b/ethers-middleware/src/gas_oracle/provider_oracle.rs @@ -0,0 +1,40 @@ +use crate::gas_oracle::{GasOracle, GasOracleError}; +use async_trait::async_trait; +use ethers_core::types::U256; +use ethers_providers::Middleware; +use std::fmt::Debug; + +/// Gas oracle from a [`Middleware`] implementation such as an +/// Ethereum RPC provider. +#[derive(Debug)] +pub struct ProviderOracle { + provider: M, +} + +impl ProviderOracle { + 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 GasOracle for ProviderOracle +where + ::Error: 'static, +{ + async fn fetch(&self) -> Result { + self.provider + .get_gas_price() + .await + .map_err(|err| GasOracleError::ProviderError(Box::new(err))) + } + + async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> { + // TODO: Allow configuring different estimation functions. + self.provider + .estimate_eip1559_fees(None) + .await + .map_err(|err| GasOracleError::ProviderError(Box::new(err))) + } +}