More gas oracles (#1251)

* Deriver auto_impls for GasOracle

* Provider as GasOracle

* impl fill_transaction in GasOracleMiddleware
This commit is contained in:
Remco Bloemen 2022-08-20 16:04:08 -07:00 committed by GitHub
parent 0707270a05
commit 49b4ac7acb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 21 deletions

17
Cargo.lock generated
View File

@ -135,6 +135,18 @@ dependencies = [
"winapi", "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]] [[package]]
name = "auto_impl" name = "auto_impl"
version = "1.0.1" version = "1.0.1"
@ -1365,6 +1377,7 @@ name = "ethers-middleware"
version = "0.17.0" version = "0.17.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"auto_impl 0.5.0",
"ethers-contract", "ethers-contract",
"ethers-core", "ethers-core",
"ethers-etherscan", "ethers-etherscan",
@ -1393,7 +1406,7 @@ name = "ethers-providers"
version = "0.17.0" version = "0.17.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"auto_impl", "auto_impl 1.0.1",
"base64 0.13.0", "base64 0.13.0",
"bytes", "bytes",
"ethers-core", "ethers-core",
@ -1547,7 +1560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "089263294bb1c38ac73649a6ad563dd9a5142c8dc0482be15b8b9acb22a1611e" checksum = "089263294bb1c38ac73649a6ad563dd9a5142c8dc0482be15b8b9acb22a1611e"
dependencies = [ dependencies = [
"arrayvec 0.7.2", "arrayvec 0.7.2",
"auto_impl", "auto_impl 1.0.1",
"bytes", "bytes",
"ethereum-types", "ethereum-types",
"fastrlp-derive", "fastrlp-derive",

View File

@ -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 } ethers-signers = { version = "^0.17.0", path = "../ethers-signers", default-features = false }
async-trait = { version = "0.1.50", 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"] } serde = { version = "1.0.124", default-features = false, features = ["derive"] }
thiserror = { version = "1.0", default-features = false } thiserror = { version = "1.0", default-features = false }
futures-util = { version = "^0.3" } futures-util = { version = "^0.3" }

View File

@ -23,7 +23,7 @@ struct GasNowResponseWrapper {
data: GasNowResponse, data: GasNowResponse,
} }
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd)] #[derive(Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct GasNowResponse { pub struct GasNowResponse {
pub rapid: u64, pub rapid: u64,
pub fast: u64, pub fast: u64,
@ -41,7 +41,7 @@ impl GasNow {
pub fn with_client(client: Client) -> Self { pub fn with_client(client: Client) -> Self {
let url = Url::parse(GAS_NOW_URL).expect("invalid url"); 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. /// Sets the gas price category to be used when fetching the gas price.
@ -64,7 +64,7 @@ impl GasNow {
impl Default for GasNow { impl Default for GasNow {
fn default() -> Self { fn default() -> Self {
Self::new(Client::new()) Self::new()
} }
} }

View File

@ -56,24 +56,11 @@ where
&self.inner &self.inner
} }
async fn get_gas_price(&self) -> Result<U256, Self::Error> { async fn fill_transaction(
Ok(self.gas_oracle.fetch().await?)
}
async fn estimate_eip1559_fees(
&self, &self,
_: Option<fn(U256, Vec<Vec<U256>>) -> (U256, U256)>, tx: &mut TypedTransaction,
) -> Result<(U256, U256), Self::Error> {
Ok(self.gas_oracle.estimate_eip1559_fees().await?)
}
async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self,
tx: T,
block: Option<BlockId>, block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> { ) -> Result<(), Self::Error> {
let mut tx = tx.into();
match tx { match tx {
TypedTransaction::Legacy(ref mut tx) => { TypedTransaction::Legacy(ref mut tx) => {
if tx.gas_price.is_none() { 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<U256, Self::Error> {
Ok(self.gas_oracle.fetch().await?)
}
async fn estimate_eip1559_fees(
&self,
_: Option<fn(U256, Vec<Vec<U256>>) -> (U256, U256)>,
) -> Result<(U256, U256), Self::Error> {
Ok(self.gas_oracle.estimate_eip1559_fees().await?)
}
async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self,
tx: T,
block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, 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) self.inner.send_transaction(tx, block).await.map_err(MiddlewareError::MiddlewareError)
} }
} }

View File

@ -22,10 +22,18 @@ pub use cache::Cache;
mod polygon; mod polygon;
pub use polygon::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 ethers_core::types::U256;
use async_trait::async_trait; use async_trait::async_trait;
use auto_impl::auto_impl;
use reqwest::Error as ReqwestError; use reqwest::Error as ReqwestError;
use std::error::Error;
use thiserror::Error; use thiserror::Error;
const GWEI_TO_WEI: u64 = 1000000000; const GWEI_TO_WEI: u64 = 1000000000;
@ -73,6 +81,10 @@ pub enum GasOracleError {
#[error("Chain is not supported by the oracle")] #[error("Chain is not supported by the oracle")]
UnsupportedChain, UnsupportedChain,
/// Error thrown when the provider failed.
#[error("Chain is not supported by the oracle")]
ProviderError(#[from] Box<dyn Error + Send + Sync>),
} }
/// `GasOracle` is a trait that an underlying gas oracle needs to implement. /// `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(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[auto_impl(&, Box, Arc)]
pub trait GasOracle: Send + Sync + std::fmt::Debug { pub trait GasOracle: Send + Sync + std::fmt::Debug {
/// Makes an asynchronous HTTP query to the underlying `GasOracle` /// Makes an asynchronous HTTP query to the underlying `GasOracle`
/// ///

View File

@ -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<M: Middleware> {
provider: M,
}
impl<M: Middleware> ProviderOracle<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 ProviderOracle<M>
where
<M as Middleware>::Error: 'static,
{
async fn fetch(&self) -> Result<U256, GasOracleError> {
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)))
}
}