fix(middleware): oracles, tests (#1944)

* fix(middleware): oracles, tests

* fix: use util

* fix: make blocknative api key optional

* make response fields public

* import orders

* docs: oracle trait

* fix: make response fns public

* chore: clippy

* fix: doc tests
This commit is contained in:
DaniPopes 2022-12-18 12:45:47 +01:00 committed by GitHub
parent 022f082cee
commit bb4af1c134
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 753 additions and 653 deletions

View File

@ -23,13 +23,13 @@ use ethers_providers::{Middleware, Provider, Http};
use std::sync::Arc;
use std::convert::TryFrom;
use ethers_signers::{LocalWallet, Signer};
use ethers_middleware::{*,gas_oracle::*};
use ethers_middleware::{gas_oracle::{GasOracle, GasNow}, MiddlewareBuilder};
fn builder_example() {
let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
let signer = key.parse::<LocalWallet>().unwrap();
let address = signer.address();
let gas_oracle = EthGasStation::new(None);
let gas_oracle = GasNow::new();
let provider = Provider::<Http>::try_from("http://localhost:8545")
.unwrap()
@ -58,7 +58,7 @@ fn builder_example_wrap_into() {
.unwrap()
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
.wrap_into(|p| SignerMiddleware::new(p, signer))
.wrap_into(|p| GasOracleMiddleware::new(p, EthGasStation::new(None)))
.wrap_into(|p| GasOracleMiddleware::new(p, GasNow::new()))
.wrap_into(|p| NonceManagerMiddleware::new(p, address)); // Outermost layer
}
```
@ -72,7 +72,7 @@ use ethers_providers::{Provider, Http};
use ethers_signers::{LocalWallet, Signer};
use ethers_middleware::{
gas_escalator::{GasEscalatorMiddleware, GeometricGasPrice, Frequency},
gas_oracle::{GasOracleMiddleware, EthGasStation, GasCategory},
gas_oracle::{GasOracleMiddleware, GasCategory, GasNow},
signer::SignerMiddleware,
nonce_manager::NonceManagerMiddleware,
};
@ -91,8 +91,8 @@ let signer = LocalWallet::new(&mut rand::thread_rng());
let address = signer.address();
let provider = SignerMiddleware::new(provider, signer);
// Use EthGasStation as the gas oracle
let gas_oracle = EthGasStation::new(None);
// Use GasNow as the gas oracle
let gas_oracle = GasNow::new();
let provider = GasOracleMiddleware::new(provider, gas_oracle);
// Manage nonces locally

View File

@ -16,14 +16,14 @@ use ethers_signers::Signer;
/// use std::sync::Arc;
/// use std::convert::TryFrom;
/// use ethers_signers::{LocalWallet, Signer};
/// use ethers_middleware::{*,gas_escalator::*,gas_oracle::*};
/// use ethers_middleware::{*, gas_escalator::*, gas_oracle::*};
///
/// fn builder_example() {
/// let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
/// let signer = key.parse::<LocalWallet>().unwrap();
/// let address = signer.address();
/// let escalator = GeometricGasPrice::new(1.125, 60_u64, None::<u64>);
/// let gas_oracle = EthGasStation::new(None);
/// let gas_oracle = GasNow::new();
///
/// let provider = Provider::<Http>::try_from("http://localhost:8545")
/// .unwrap()
@ -43,7 +43,7 @@ use ethers_signers::Signer;
/// .unwrap()
/// .wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
/// .wrap_into(|p| SignerMiddleware::new(p, signer))
/// .wrap_into(|p| GasOracleMiddleware::new(p, EthGasStation::new(None)))
/// .wrap_into(|p| GasOracleMiddleware::new(p, GasNow::new()))
/// .wrap_into(|p| NonceManagerMiddleware::new(p, address)); // Outermost layer
/// }
/// ```

View File

@ -46,7 +46,7 @@ pub enum Frequency {
/// use ethers_providers::{Provider, Http};
/// use ethers_middleware::{
/// gas_escalator::{GeometricGasPrice, Frequency, GasEscalatorMiddleware},
/// gas_oracle::{EthGasStation, GasCategory, GasOracleMiddleware},
/// gas_oracle::{GasNow, GasCategory, GasOracleMiddleware},
/// };
/// use std::{convert::TryFrom, time::Duration, sync::Arc};
///
@ -60,7 +60,7 @@ pub enum Frequency {
/// };
///
/// // ... proceed to wrap it in other middleware
/// let gas_oracle = EthGasStation::new(None).category(GasCategory::SafeLow);
/// let gas_oracle = GasNow::new().category(GasCategory::SafeLow);
/// let provider = GasOracleMiddleware::new(provider, gas_oracle);
/// ```
pub struct GasEscalatorMiddleware<M, E> {

View File

@ -1,13 +1,128 @@
use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError, GWEI_TO_WEI};
use super::{from_gwei_f64, GasCategory, GasOracle, GasOracleError, Result, GWEI_TO_WEI_U256};
use async_trait::async_trait;
use ethers_core::types::U256;
use reqwest::{header::AUTHORIZATION, Client};
use serde::Deserialize;
use std::{collections::HashMap, convert::TryInto};
use std::collections::HashMap;
use url::Url;
const BLOCKNATIVE_GAS_PRICE_ENDPOINT: &str = "https://api.blocknative.com/gasprices/blockprices";
const URL: &str = "https://api.blocknative.com/gasprices/blockprices";
/// A client over HTTP for the [BlockNative](https://www.blocknative.com/gas-estimator) gas tracker API
/// that implements the `GasOracle` trait.
#[derive(Clone, Debug)]
#[must_use]
pub struct BlockNative {
client: Client,
url: Url,
api_key: Option<String>,
gas_category: GasCategory,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Response {
pub system: String,
pub network: String,
pub unit: String,
pub max_price: u64,
pub block_prices: Vec<BlockPrice>,
pub estimated_base_fees: Option<Vec<HashMap<String, Vec<BaseFeeEstimate>>>>,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BlockPrice {
pub block_number: u64,
pub estimated_transaction_count: u64,
pub base_fee_per_gas: f64,
pub estimated_prices: Vec<GasEstimate>,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GasEstimate {
pub confidence: u64,
pub price: u64,
pub max_priority_fee_per_gas: f64,
pub max_fee_per_gas: f64,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BaseFeeEstimate {
pub confidence: u64,
pub base_fee: f64,
}
impl Response {
#[inline]
pub fn estimate_from_category(&self, gas_category: &GasCategory) -> Result<GasEstimate> {
let confidence = gas_category_to_confidence(gas_category);
let price = self
.block_prices
.first()
.ok_or(GasOracleError::InvalidResponse)?
.estimated_prices
.iter()
.find(|p| p.confidence == confidence)
.ok_or(GasOracleError::GasCategoryNotSupported)?;
Ok(*price)
}
}
impl Default for BlockNative {
fn default() -> Self {
Self::new(None)
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for BlockNative {
async fn fetch(&self) -> Result<U256> {
let estimate = self.query().await?.estimate_from_category(&self.gas_category)?;
Ok(U256::from(estimate.price) * GWEI_TO_WEI_U256)
}
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
let estimate = self.query().await?.estimate_from_category(&self.gas_category)?;
let max = from_gwei_f64(estimate.max_fee_per_gas);
let prio = from_gwei_f64(estimate.max_priority_fee_per_gas);
Ok((max, prio))
}
}
impl BlockNative {
/// Creates a new [BlockNative](https://www.blocknative.com/gas-estimator) gas oracle.
pub fn new(api_key: Option<String>) -> Self {
Self::with_client(Client::new(), api_key)
}
/// Same as [`Self::new`] but with a custom [`Client`].
pub fn with_client(client: Client, api_key: Option<String>) -> Self {
let url = Url::parse(URL).unwrap();
Self { client, api_key, url, 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, GasOracleError> {
let mut request = self.client.get(self.url.clone());
if let Some(api_key) = self.api_key.as_ref() {
request = request.header(AUTHORIZATION, api_key);
}
let response = request.send().await?.error_for_status()?.json().await?;
Ok(response)
}
}
#[inline]
fn gas_category_to_confidence(gas_category: &GasCategory) -> u64 {
match gas_category {
GasCategory::SafeLow => 80,
@ -16,124 +131,3 @@ fn gas_category_to_confidence(gas_category: &GasCategory) -> u64 {
GasCategory::Fastest => 99,
}
}
/// A client over HTTP for the [BlockNative](https://www.blocknative.com/gas-estimator) gas tracker API
/// that implements the `GasOracle` trait
#[derive(Clone, Debug)]
pub struct BlockNative {
client: Client,
url: Url,
api_key: String,
gas_category: GasCategory,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BlockNativeGasResponse {
system: Option<String>,
network: Option<String>,
unit: Option<String>,
max_price: Option<u64>,
block_prices: Vec<BlockPrice>,
estimated_base_fees: Vec<HashMap<String, Vec<BaseFeeEstimate>>>,
}
impl BlockNativeGasResponse {
pub fn get_estimation_for(
&self,
gas_category: &GasCategory,
) -> Result<EstimatedPrice, GasOracleError> {
let confidence = gas_category_to_confidence(gas_category);
Ok(self
.block_prices
.first()
.ok_or(GasOracleError::InvalidResponse)?
.estimated_prices
.iter()
.find(|p| p.confidence == confidence)
.ok_or(GasOracleError::GasCategoryNotSupported)?
.clone())
}
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BlockPrice {
block_number: u64,
estimated_transaction_count: u64,
base_fee_per_gas: f64,
estimated_prices: Vec<EstimatedPrice>,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EstimatedPrice {
confidence: u64,
price: u64,
max_priority_fee_per_gas: f64,
max_fee_per_gas: f64,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BaseFeeEstimate {
confidence: u64,
base_fee: f64,
}
impl BlockNative {
/// Creates a new [BlockNative](https://www.blocknative.com/gas-estimator) gas oracle.
pub fn new(api_key: String) -> Self {
Self::with_client(Client::new(), api_key)
}
/// Same as [`Self::new`] but with a custom [`Client`].
pub fn with_client(client: Client, api_key: String) -> Self {
Self {
client,
api_key,
url: BLOCKNATIVE_GAS_PRICE_ENDPOINT.try_into().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<BlockNativeGasResponse, GasOracleError> {
self.client
.get(self.url.as_ref())
.header(AUTHORIZATION, &self.api_key)
.send()
.await?
.error_for_status()?
.json()
.await
.map_err(GasOracleError::HttpClientError)
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for BlockNative {
async fn fetch(&self) -> Result<U256, GasOracleError> {
let prices = self.request().await?.get_estimation_for(&self.gas_category)?;
Ok(U256::from(prices.price * 100_u64) * U256::from(GWEI_TO_WEI) / U256::from(100))
}
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
let prices = self.request().await?.get_estimation_for(&self.gas_category)?;
let base_fee = U256::from((prices.max_fee_per_gas * 100.0) as u64) *
U256::from(GWEI_TO_WEI) /
U256::from(100);
let prio_fee = U256::from((prices.max_priority_fee_per_gas * 100.0) as u64) *
U256::from(GWEI_TO_WEI) /
U256::from(100);
Ok((base_fee, prio_fee))
}
}

View File

@ -1,4 +1,4 @@
use crate::gas_oracle::{GasOracle, GasOracleError};
use super::{GasOracle, Result};
use async_trait::async_trait;
use ethers_core::types::U256;
use futures_locks::RwLock;
@ -19,6 +19,24 @@ pub struct Cache<T: GasOracle> {
#[derive(Default, Debug)]
struct Cached<T: Clone>(RwLock<Option<(Instant, T)>>);
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<T: GasOracle> GasOracle for Cache<T> {
async fn fetch(&self) -> Result<U256> {
self.fee.get(self.validity, || self.inner.fetch()).await
}
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
self.eip1559.get(self.validity, || self.inner.estimate_eip1559_fees()).await
}
}
impl<T: GasOracle> Cache<T> {
pub fn new(validity: Duration, inner: T) -> Self {
Self { inner, validity, fee: Cached::default(), eip1559: Cached::default() }
}
}
impl<T: Clone> Cached<T> {
async fn get<F, E, Fut>(&self, validity: Duration, fetch: F) -> Result<T, E>
where
@ -50,21 +68,3 @@ impl<T: Clone> Cached<T> {
}
}
}
impl<T: GasOracle> Cache<T> {
pub fn new(validity: Duration, inner: T) -> Self {
Self { inner, validity, fee: Cached::default(), eip1559: Cached::default() }
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<T: GasOracle> GasOracle for Cache<T> {
async fn fetch(&self) -> Result<U256, GasOracleError> {
self.fee.get(self.validity, || self.inner.fetch()).await
}
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
self.eip1559.get(self.validity, || self.inner.estimate_eip1559_fees()).await
}
}

View File

@ -1,88 +1,78 @@
use std::collections::HashMap;
use ethers_core::types::U256;
#![allow(deprecated)]
use super::{GasCategory, GasOracle, GasOracleError, Result, GWEI_TO_WEI_U256};
use async_trait::async_trait;
use ethers_core::types::U256;
use reqwest::Client;
use serde::Deserialize;
use std::collections::HashMap;
use url::Url;
use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError, GWEI_TO_WEI};
const URL: &str = "https://ethgasstation.info/api/ethgasAPI.json";
const ETH_GAS_STATION_URL_PREFIX: &str = "https://ethgasstation.info/api/ethgasAPI.json";
/// A client over HTTP for the [EthGasStation](https://ethgasstation.info/api/ethgasAPI.json) gas tracker API
/// that implements the `GasOracle` trait
/// A client over HTTP for the [EthGasStation](https://ethgasstation.info) gas tracker API
/// that implements the `GasOracle` trait.
#[derive(Clone, Debug)]
#[deprecated = "ETHGasStation is shutting down: https://twitter.com/ETHGasStation/status/1597341610777317376"]
#[must_use]
pub struct EthGasStation {
client: Client,
url: Url,
gas_category: GasCategory,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
/// Eth Gas Station's response for the current recommended fast, standard and
/// safe low gas prices on the Ethereum network, along with the current block
/// and wait times for each "speed".
pub struct EthGasStationResponse {
/// Recommended safe(expected to be mined in < 30 minutes) gas price in
/// x10 Gwei (divide by 10 to convert it to gwei)
pub safe_low: f64,
/// Recommended average(expected to be mined in < 5 minutes) gas price in
/// x10 Gwei (divide by 10 to convert it to gwei)
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Response {
/// Recommended safe (expected to be mined in < 30 minutes).
///
/// In gwei * 10 (divide by 10 to convert it to gwei).
pub safe_low: u64,
/// Recommended average (expected to be mined in < 5 minutes).
///
/// In gwei * 10 (divide by 10 to convert it to gwei).
pub average: u64,
/// Recommended fast(expected to be mined in < 2 minutes) gas price in
/// x10 Gwei (divide by 10 to convert it to gwei)
/// Recommended fast (expected to be mined in < 2 minutes).
///
/// In gwei * 10 (divide by 10 to convert it to gwei).
pub fast: u64,
/// Recommended fastest(expected to be mined in < 30 seconds) gas price
/// in x10 Gwei(divide by 10 to convert it to gwei)
/// Recommended fastest (expected to be mined in < 30 seconds).
///
/// In gwei * 10 (divide by 10 to convert it to gwei).
pub fastest: u64,
// post eip-1559 fields
/// Average time (in seconds) to mine a single block.
#[serde(rename = "block_time")] // inconsistent json response naming...
/// Average time(in seconds) to mine one single block
pub block_time: f64,
/// The latest block number
/// The latest block number.
pub block_num: u64,
/// Smallest value of (gasUsed / gaslimit) from last 10 blocks
/// Smallest value of `gasUsed / gaslimit` from the last 10 blocks.
pub speed: f64,
/// Waiting time(in minutes) for the `safe_low` gas price
/// Waiting time (in minutes) for the `safe_low` gas price.
pub safe_low_wait: f64,
/// Waiting time(in minutes) for the `average` gas price
/// Waiting time (in minutes) for the `average` gas price.
pub avg_wait: f64,
/// Waiting time(in minutes) for the `fast` gas price
/// Waiting time (in minutes) for the `fast` gas price.
pub fast_wait: f64,
/// Waiting time(in minutes) for the `fastest` gas price
/// Waiting time (in minutes) for the `fastest` gas price.
pub fastest_wait: f64,
// What is this?
pub gas_price_range: HashMap<u64, f64>,
}
impl EthGasStation {
/// Creates a new [EthGasStation](https://docs.ethgasstation.info/) gas oracle
pub fn new(api_key: Option<&str>) -> Self {
Self::with_client(Client::new(), api_key)
}
/// Same as [`Self::new`] but with a custom [`Client`].
pub fn with_client(client: Client, api_key: Option<&str>) -> Self {
let mut url = Url::parse(ETH_GAS_STATION_URL_PREFIX).expect("invalid url");
if let Some(key) = api_key {
url.query_pairs_mut().append_pair("api-key", key);
impl Response {
#[inline]
pub fn gas_from_category(&self, gas_category: GasCategory) -> u64 {
match gas_category {
GasCategory::SafeLow => self.safe_low,
GasCategory::Standard => self.average,
GasCategory::Fast => self.fast,
GasCategory::Fastest => self.fastest,
}
EthGasStation { client, url, 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
}
pub async fn query(&self) -> Result<EthGasStationResponse, GasOracleError> {
Ok(self.client.get(self.url.as_ref()).send().await?.json::<EthGasStationResponse>().await?)
}
}
@ -95,19 +85,43 @@ impl Default for EthGasStation {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for EthGasStation {
async fn fetch(&self) -> Result<U256, GasOracleError> {
async fn fetch(&self) -> Result<U256> {
let res = self.query().await?;
let gas_price = match self.gas_category {
GasCategory::SafeLow => U256::from((res.safe_low.ceil() as u64 * GWEI_TO_WEI) / 10),
GasCategory::Standard => U256::from((res.average * GWEI_TO_WEI) / 10),
GasCategory::Fast => U256::from((res.fast * GWEI_TO_WEI) / 10),
GasCategory::Fastest => U256::from((res.fastest * GWEI_TO_WEI) / 10),
};
Ok(gas_price)
let gas_price = res.gas_from_category(self.gas_category);
// gas_price is in `gwei * 10`
Ok(U256::from(gas_price) * GWEI_TO_WEI_U256 / U256::from(10_u64))
}
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
Err(GasOracleError::Eip1559EstimationNotSupported)
}
}
impl EthGasStation {
/// Creates a new [EthGasStation](https://docs.ethgasstation.info/) gas oracle.
pub fn new(api_key: Option<&str>) -> Self {
Self::with_client(Client::new(), api_key)
}
/// Same as [`Self::new`] but with a custom [`Client`].
pub fn with_client(client: Client, api_key: Option<&str>) -> Self {
let mut url = Url::parse(URL).unwrap();
if let Some(key) = api_key {
url.query_pairs_mut().append_pair("api-key", key);
}
EthGasStation { client, url, 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)
}
}

View File

@ -1,56 +1,42 @@
use ethers_core::types::U256;
use super::{from_gwei_f64, GasCategory, GasOracle, GasOracleError, Result};
use async_trait::async_trait;
use ethers_core::types::U256;
use reqwest::Client;
use serde::Deserialize;
use url::Url;
use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError, GWEI_TO_WEI};
const ETHERCHAIN_URL: &str = "https://www.etherchain.org/api/gasPriceOracle";
const URL: &str = "https://www.etherchain.org/api/gasPriceOracle";
/// A client over HTTP for the [Etherchain](https://www.etherchain.org/api/gasPriceOracle) gas tracker API
/// that implements the `GasOracle` trait
/// that implements the `GasOracle` trait.
#[derive(Clone, Debug)]
#[must_use]
pub struct Etherchain {
client: Client,
url: Url,
gas_category: GasCategory,
}
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EtherchainResponse {
pub safe_low: f32,
pub standard: f32,
pub fast: f32,
pub fastest: f32,
pub current_base_fee: f32,
pub recommended_base_fee: f32,
pub struct Response {
pub safe_low: f64,
pub standard: f64,
pub fast: f64,
pub fastest: f64,
pub current_base_fee: f64,
pub recommended_base_fee: f64,
}
impl Etherchain {
/// Creates a new [Etherchain](https://etherchain.org/tools/gasPriceOracle) gas price oracle.
pub fn new() -> Self {
Self::with_client(Client::new())
}
/// Same as [`Self::new`] but with a custom [`Client`].
pub fn with_client(client: Client) -> Self {
let url = Url::parse(ETHERCHAIN_URL).expect("invalid url");
Etherchain { client, url, 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
}
pub async fn query(&self) -> Result<EtherchainResponse, GasOracleError> {
Ok(self.client.get(self.url.as_ref()).send().await?.json::<EtherchainResponse>().await?)
impl Response {
#[inline]
pub fn gas_from_category(&self, gas_category: GasCategory) -> f64 {
match gas_category {
GasCategory::SafeLow => self.safe_low,
GasCategory::Standard => self.standard,
GasCategory::Fast => self.fast,
GasCategory::Fastest => self.fastest,
}
}
}
@ -63,19 +49,39 @@ impl Default for Etherchain {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for Etherchain {
async fn fetch(&self) -> Result<U256, GasOracleError> {
async fn fetch(&self) -> Result<U256> {
let res = self.query().await?;
let gas_price = match self.gas_category {
GasCategory::SafeLow => U256::from((res.safe_low as u64) * GWEI_TO_WEI),
GasCategory::Standard => U256::from((res.standard as u64) * GWEI_TO_WEI),
GasCategory::Fast => U256::from((res.fast as u64) * GWEI_TO_WEI),
GasCategory::Fastest => U256::from((res.fastest as u64) * GWEI_TO_WEI),
};
Ok(gas_price)
let gas_price = res.gas_from_category(self.gas_category);
Ok(from_gwei_f64(gas_price))
}
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
Err(GasOracleError::Eip1559EstimationNotSupported)
}
}
impl Etherchain {
/// Creates a new [Etherchain](https://etherchain.org/tools/gasPriceOracle) gas price oracle.
pub fn new() -> Self {
Self::with_client(Client::new())
}
/// Same as [`Self::new`] but with a custom [`Client`].
pub fn with_client(client: Client) -> Self {
let url = Url::parse(URL).unwrap();
Etherchain { client, url, 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)
}
}

View File

@ -1,18 +1,57 @@
use super::{GasCategory, GasOracle, GasOracleError, Result, GWEI_TO_WEI_U256};
use async_trait::async_trait;
use ethers_core::types::U256;
use ethers_etherscan::Client;
use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError, GWEI_TO_WEI};
use std::ops::{Deref, DerefMut};
/// A client over HTTP for the [Etherscan](https://api.etherscan.io/api?module=gastracker&action=gasoracle) gas tracker API
/// that implements the `GasOracle` trait
#[derive(Clone, Debug)]
#[must_use]
pub struct Etherscan {
client: Client,
gas_category: GasCategory,
}
impl Deref for Etherscan {
type Target = Client;
fn deref(&self) -> &Self::Target {
&self.client
}
}
impl DerefMut for Etherscan {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.client
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for Etherscan {
async fn fetch(&self) -> Result<U256> {
// handle unsupported gas categories before making the request
match self.gas_category {
GasCategory::SafeLow | GasCategory::Standard | GasCategory::Fast => {}
GasCategory::Fastest => return Err(GasOracleError::GasCategoryNotSupported),
}
let result = self.query().await?;
let gas_price = match self.gas_category {
GasCategory::SafeLow => result.safe_gas_price,
GasCategory::Standard => result.propose_gas_price,
GasCategory::Fast => result.fast_gas_price,
_ => unreachable!(),
};
Ok(U256::from(gas_price) * GWEI_TO_WEI_U256)
}
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
Err(GasOracleError::Eip1559EstimationNotSupported)
}
}
impl Etherscan {
/// Creates a new [Etherscan](https://etherscan.io/gastracker) gas price oracle.
pub fn new(client: Client) -> Self {
@ -20,32 +59,13 @@ impl Etherscan {
}
/// 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
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for Etherscan {
async fn fetch(&self) -> Result<U256, GasOracleError> {
if matches!(self.gas_category, GasCategory::Fastest) {
return Err(GasOracleError::GasCategoryNotSupported)
}
let result = self.client.gas_oracle().await?;
match self.gas_category {
GasCategory::SafeLow => Ok(U256::from(result.safe_gas_price * GWEI_TO_WEI)),
GasCategory::Standard => Ok(U256::from(result.propose_gas_price * GWEI_TO_WEI)),
GasCategory::Fast => Ok(U256::from(result.fast_gas_price * GWEI_TO_WEI)),
_ => Err(GasOracleError::GasCategoryNotSupported),
}
}
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
Err(GasOracleError::Eip1559EstimationNotSupported)
/// Perform a request to the gas price API and deserialize the response.
pub async fn query(&self) -> Result<ethers_etherscan::gas::GasOracle> {
Ok(self.client.gas_oracle().await?)
}
}

View File

@ -1,64 +1,54 @@
use ethers_core::types::U256;
use super::{GasCategory, GasOracle, GasOracleError, Result};
use async_trait::async_trait;
use ethers_core::types::U256;
use reqwest::Client;
use serde::Deserialize;
use url::Url;
use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError};
const URL: &str = "https://beaconcha.in/api/v1/execution/gasnow";
const GAS_NOW_URL: &str = "https://etherchain.org/api/gasnow";
/// A client over HTTP for the [Etherchain GasNow](https://etherchain.org/tools/gasnow) gas tracker API
/// that implements the `GasOracle` trait
/// A client over HTTP for the [beaconcha.in GasNow](https://beaconcha.in/gasnow) gas tracker API
/// that implements the `GasOracle` trait.
#[derive(Clone, Debug)]
#[must_use]
pub struct GasNow {
client: Client,
url: Url,
gas_category: GasCategory,
}
#[derive(Deserialize)]
struct GasNowResponseWrapper {
data: GasNowResponse,
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
pub struct Response {
pub code: u64,
pub data: ResponseData,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct GasNowResponse {
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
pub struct ResponseData {
pub rapid: u64,
pub fast: u64,
pub standard: u64,
pub slow: u64,
pub timestamp: u64,
#[serde(rename = "priceUSD")]
pub price_usd: f64,
}
impl GasNow {
/// Creates a new [Etherchain GasNow](https://etherchain.org/tools/gasnow) gas price oracle.
pub fn new() -> Self {
Self::with_client(Client::new())
impl Response {
#[inline]
pub fn gas_from_category(&self, gas_category: GasCategory) -> u64 {
self.data.gas_from_category(gas_category)
}
}
/// Same as [`Self::new`] but with a custom [`Client`].
pub fn with_client(client: Client) -> Self {
let url = Url::parse(GAS_NOW_URL).expect("invalid url");
Self { client, url, 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
}
pub async fn query(&self) -> Result<GasNowResponse, GasOracleError> {
let res = self
.client
.get(self.url.as_ref())
.send()
.await?
.json::<GasNowResponseWrapper>()
.await?;
Ok(res.data)
impl ResponseData {
fn gas_from_category(&self, gas_category: GasCategory) -> u64 {
match gas_category {
GasCategory::SafeLow => self.slow,
GasCategory::Standard => self.standard,
GasCategory::Fast => self.fast,
GasCategory::Fastest => self.rapid,
}
}
}
@ -71,19 +61,39 @@ impl Default for GasNow {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for GasNow {
async fn fetch(&self) -> Result<U256, GasOracleError> {
async fn fetch(&self) -> Result<U256> {
let res = self.query().await?;
let gas_price = match self.gas_category {
GasCategory::SafeLow => U256::from(res.slow),
GasCategory::Standard => U256::from(res.standard),
GasCategory::Fast => U256::from(res.fast),
GasCategory::Fastest => U256::from(res.rapid),
};
Ok(gas_price)
let gas_price = res.gas_from_category(self.gas_category);
Ok(U256::from(gas_price))
}
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
Err(GasOracleError::Eip1559EstimationNotSupported)
}
}
impl GasNow {
/// Creates a new [beaconcha.in GasNow](https://beaconcha.in/gasnow) gas price oracle.
pub fn new() -> Self {
Self::with_client(Client::new())
}
/// Same as [`Self::new`] but with a custom [`Client`].
pub fn with_client(client: Client) -> Self {
let url = Url::parse(URL).unwrap();
Self { client, url, 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.as_ref()).send().await?.error_for_status()?.json().await?;
Ok(response)
}
}

View File

@ -1,4 +1,4 @@
use crate::gas_oracle::{GasOracle, GasOracleError};
use super::{GasOracle, GasOracleError, Result};
use async_trait::async_trait;
use ethers_core::types::U256;
use futures_util::future::join_all;
@ -28,13 +28,10 @@ impl Median {
self.oracles.push((weight, Box::new(oracle)));
}
pub async fn query_all<'a, Fn, Fut, O>(
&'a self,
mut f: Fn,
) -> Result<Vec<(f32, O)>, GasOracleError>
pub async fn query_all<'a, Fn, Fut, O>(&'a self, mut f: Fn) -> Result<Vec<(f32, O)>>
where
Fn: FnMut(&'a dyn GasOracle) -> Fut,
Fut: Future<Output = Result<O, GasOracleError>>,
Fut: Future<Output = Result<O>>,
{
// Process the oracles in parallel
let futures = self.oracles.iter().map(|(_, oracle)| f(oracle.as_ref()));
@ -62,13 +59,13 @@ impl Median {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for Median {
async fn fetch(&self) -> Result<U256, GasOracleError> {
async fn fetch(&self) -> Result<U256> {
let mut values = self.query_all(|oracle| oracle.fetch()).await?;
// `query_all` guarantees `values` is not empty
Ok(*weighted_fractile_by_key(0.5, &mut values, |fee| fee).unwrap())
}
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
let mut values = self.query_all(|oracle| oracle.estimate_eip1559_fees()).await?;
// `query_all` guarantees `values` is not empty
Ok((

View File

@ -4,8 +4,8 @@ use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
use ethers_providers::{FromErr, Middleware, PendingTransaction};
use thiserror::Error;
/// Middleware used for fetching gas prices over an API instead of `eth_gasPrice`.
#[derive(Debug)]
/// Middleware used for fetching gas prices over an API instead of `eth_gasPrice`
pub struct GasOracleMiddleware<M, G> {
inner: M,
gas_oracle: G,
@ -21,7 +21,7 @@ where
}
}
#[derive(Error, Debug)]
#[derive(Debug, Error)]
pub enum MiddlewareError<M: Middleware> {
#[error(transparent)]
GasOracleError(#[from] GasOracleError),

View File

@ -1,54 +1,58 @@
mod blocknative;
pub mod blocknative;
pub use blocknative::BlockNative;
mod eth_gas_station;
pub mod eth_gas_station;
#[allow(deprecated)]
pub use eth_gas_station::EthGasStation;
mod etherchain;
pub mod etherchain;
pub use etherchain::Etherchain;
mod etherscan;
pub mod etherscan;
pub use etherscan::Etherscan;
mod middleware;
pub mod middleware;
pub use middleware::{GasOracleMiddleware, MiddlewareError};
mod median;
pub mod median;
pub use median::Median;
mod cache;
pub mod cache;
pub use cache::Cache;
mod polygon;
pub mod polygon;
pub use polygon::Polygon;
mod gas_now;
pub mod gas_now;
pub use gas_now::GasNow;
mod provider_oracle;
pub mod provider_oracle;
pub use provider_oracle::ProviderOracle;
use ethers_core::types::U256;
use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers_core::types::U256;
use reqwest::Error as ReqwestError;
use std::error::Error;
use std::{error::Error, fmt::Debug};
use thiserror::Error;
const GWEI_TO_WEI: u64 = 1000000000;
pub(crate) const GWEI_TO_WEI: u64 = 1_000_000_000;
pub(crate) const GWEI_TO_WEI_U256: U256 = U256([0, 0, 0, GWEI_TO_WEI]);
/// Various gas price categories. Choose one of the available
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub type Result<T, E = GasOracleError> = std::result::Result<T, E>;
/// Generic [`GasOracle`] gas price categories.
#[derive(Clone, Copy, Default, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum GasCategory {
SafeLow,
#[default]
Standard,
Fast,
Fastest,
}
#[derive(Error, Debug)]
/// Error thrown when fetching data from the `GasOracle`
/// Error thrown by a [`GasOracle`].
#[derive(Debug, Error)]
pub enum GasOracleError {
/// An internal error in the HTTP request made from the underlying
/// gas oracle
@ -83,48 +87,69 @@ pub enum GasOracleError {
UnsupportedChain,
/// Error thrown when the provider failed.
#[error("Chain is not supported by the oracle")]
#[error("Provider error: {0}")]
ProviderError(#[from] Box<dyn Error + Send + Sync>),
}
/// `GasOracle` is a trait that an underlying gas oracle needs to implement.
/// An Ethereum gas price oracle.
///
/// # Example
///
/// ```no_run
/// use ethers_middleware::{
/// gas_oracle::{EthGasStation, Etherscan, GasCategory, GasOracle},
/// };
/// use ethers_core::types::U256;
/// use ethers_middleware::gas_oracle::{GasCategory, GasNow, GasOracle};
///
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// let eth_gas_station_oracle = EthGasStation::new(Some("my-api-key"));
/// let etherscan_oracle = EthGasStation::new(None).category(GasCategory::SafeLow);
///
/// let data_1 = eth_gas_station_oracle.fetch().await?;
/// let data_2 = etherscan_oracle.fetch().await?;
/// let oracle = GasNow::default().category(GasCategory::SafeLow);
/// let gas_price = oracle.fetch().await?;
/// assert!(gas_price > U256::zero());
/// # Ok(())
/// # }
/// ```
#[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`
pub trait GasOracle: Send + Sync + Debug {
/// Makes an asynchronous HTTP query to the underlying [`GasOracle`] to fetch the current gas
/// price estimate.
///
/// # Example
///
/// ```
/// use ethers_middleware::{
/// gas_oracle::{Etherchain, GasCategory, GasOracle},
/// };
/// ```no_run
/// use ethers_core::types::U256;
/// use ethers_middleware::gas_oracle::{GasCategory, GasNow, GasOracle};
///
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// let etherchain_oracle = Etherchain::new().category(GasCategory::Fastest);
/// let data = etherchain_oracle.fetch().await?;
/// let oracle = GasNow::default().category(GasCategory::SafeLow);
/// let gas_price = oracle.fetch().await?;
/// assert!(gas_price > U256::zero());
/// # Ok(())
/// # }
/// ```
async fn fetch(&self) -> Result<U256, GasOracleError>;
async fn fetch(&self) -> Result<U256>;
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError>;
/// Makes an asynchronous HTTP query to the underlying [`GasOracle`] to fetch the current max
/// gas fee and priority gas fee estimates.
///
/// # Example
///
/// ```no_run
/// use ethers_core::types::U256;
/// use ethers_middleware::gas_oracle::{GasCategory, GasNow, GasOracle};
///
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// let oracle = GasNow::default().category(GasCategory::SafeLow);
/// let (max_fee, priority_fee) = oracle.estimate_eip1559_fees().await?;
/// assert!(max_fee > U256::zero());
/// assert!(priority_fee > U256::zero());
/// # Ok(())
/// # }
/// ```
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)>;
}
#[inline]
#[doc(hidden)]
pub(crate) fn from_gwei_f64(gwei: f64) -> U256 {
ethers_core::types::u256_from_f64_saturating(gwei) * GWEI_TO_WEI_U256
}

View File

@ -1,16 +1,17 @@
use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError};
use super::{from_gwei_f64, GasCategory, GasOracle, GasOracleError, Result};
use async_trait::async_trait;
use ethers_core::types::{u256_from_f64_saturating, Chain, U256};
use ethers_core::types::{Chain, U256};
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-mainnet.matic.network/v2";
const MUMBAI_URL: &str = "https://gasstation-mumbai.matic.today/v2";
/// The [Polygon](https://docs.polygon.technology/docs/develop/tools/polygon-gas-station/) gas station API
/// Queries over HTTP and implements the `GasOracle` trait
/// Queries over HTTP and implements the `GasOracle` trait.
#[derive(Clone, Debug)]
#[must_use]
pub struct Polygon {
client: Client,
url: Url,
@ -18,74 +19,87 @@ pub struct Polygon {
}
/// The response from the Polygon gas station API.
///
/// Gas prices are in Gwei.
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Response {
estimated_base_fee: f64,
safe_low: GasEstimate,
standard: GasEstimate,
fast: GasEstimate,
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,
pub max_priority_fee: f64,
pub max_fee: f64,
}
impl Polygon {
pub fn new(chain: Chain) -> Result<Self, GasOracleError> {
Self::with_client(Client::new(), chain)
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,
}
}
}
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(),
_ => return Err(GasOracleError::UnsupportedChain),
};
Ok(Self { client, url, 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))
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, GasOracleError> {
let (base_fee, estimate) = self.request().await?;
let fee = base_fee + estimate.max_priority_fee;
Ok(from_gwei(fee))
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), GasOracleError> {
let (_, estimate) = self.request().await?;
Ok((from_gwei(estimate.max_fee), from_gwei(estimate.max_priority_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))
}
}
fn from_gwei(gwei: f64) -> U256 {
u256_from_f64_saturating(gwei * 1.0e9_f64)
impl Polygon {
pub fn new(chain: Chain) -> Result<Self> {
Self::with_client(Client::new(), 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)
}
}

View File

@ -1,4 +1,4 @@
use crate::gas_oracle::{GasOracle, GasOracleError};
use super::{GasOracle, GasOracleError, Result};
use async_trait::async_trait;
use ethers_core::types::U256;
use ethers_providers::Middleware;
@ -6,7 +6,8 @@ use std::fmt::Debug;
/// Gas oracle from a [`Middleware`] implementation such as an
/// Ethereum RPC provider.
#[derive(Debug)]
#[derive(Clone, Debug)]
#[must_use]
pub struct ProviderOracle<M: Middleware> {
provider: M,
}
@ -21,16 +22,16 @@ impl<M: Middleware> ProviderOracle<M> {
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<M: Middleware> GasOracle for ProviderOracle<M>
where
<M as Middleware>::Error: 'static,
M::Error: 'static,
{
async fn fetch(&self) -> Result<U256, GasOracleError> {
async fn fetch(&self) -> Result<U256> {
self.provider
.get_gas_price()
.await
.map_err(|err| GasOracleError::ProviderError(Box::new(err)))
}
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
// TODO: Allow configuring different estimation functions.
self.provider
.estimate_eip1559_fees(None)

View File

@ -1,22 +1,21 @@
use ethers_contract::Lazy;
use ethers_core::types::*;
use std::{collections::HashMap, str::FromStr};
use std::collections::HashMap;
/// A lazily computed hash map with the Ethereum network IDs as keys and the corresponding
/// DsProxyFactory contract addresses as values
pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
let mut m = HashMap::new();
let mut m = HashMap::with_capacity(1);
// mainnet
let addr =
Address::from_str("eefba1e63905ef1d7acba5a8513c70307c1ce441").expect("Decoding failed");
m.insert(U256::from(1u8), addr);
let addr = "eefba1e63905ef1d7acba5a8513c70307c1ce441".parse().unwrap();
m.insert(U256::from(1_u64), addr);
m
});
/// Generated with abigen:
///
/// Generated with
/// ```ignore
/// # use ethers_contract::abigen;
/// abigen!(DsProxyFactory,
@ -26,7 +25,6 @@ pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
/// }
/// );
/// ```
// Auto-generated type-safe bindings
pub use dsproxyfactory_mod::*;
#[allow(clippy::too_many_arguments)]
mod dsproxyfactory_mod {

View File

@ -1,69 +1,67 @@
#![cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "celo"))]
mod tests {
use ethers_core::{rand::thread_rng, types::U64};
use ethers_middleware::{
builder::MiddlewareBuilder,
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
gas_oracle::{EthGasStation, GasOracleMiddleware},
nonce_manager::NonceManagerMiddleware,
signer::SignerMiddleware,
};
use ethers_providers::{Middleware, Provider};
use ethers_signers::{LocalWallet, Signer};
#[tokio::test]
async fn build_raw_middleware_stack() {
let (provider, mock) = Provider::mocked();
use ethers_core::{rand::thread_rng, types::U64};
use ethers_middleware::{
builder::MiddlewareBuilder,
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
gas_oracle::{GasNow, GasOracleMiddleware},
nonce_manager::NonceManagerMiddleware,
signer::SignerMiddleware,
};
use ethers_providers::{Middleware, Provider};
use ethers_signers::{LocalWallet, Signer};
let signer = LocalWallet::new(&mut thread_rng());
let address = signer.address();
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
#[tokio::test]
async fn build_raw_middleware_stack() {
let (provider, mock) = Provider::mocked();
let provider = provider
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
.wrap_into(|p| GasOracleMiddleware::new(p, EthGasStation::new(None)))
.wrap_into(|p| SignerMiddleware::new(p, signer))
.wrap_into(|p| NonceManagerMiddleware::new(p, address));
let signer = LocalWallet::new(&mut thread_rng());
let address = signer.address();
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
// push a response
mock.push(U64::from(12u64)).unwrap();
let block: U64 = provider.get_block_number().await.unwrap();
assert_eq!(block.as_u64(), 12);
let provider = provider
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
.wrap_into(|p| GasOracleMiddleware::new(p, GasNow::new()))
.wrap_into(|p| SignerMiddleware::new(p, signer))
.wrap_into(|p| NonceManagerMiddleware::new(p, address));
provider.get_block_number().await.unwrap_err();
// push a response
mock.push(U64::from(12u64)).unwrap();
let block: U64 = provider.get_block_number().await.unwrap();
assert_eq!(block.as_u64(), 12);
// 2 calls were made
mock.assert_request("eth_blockNumber", ()).unwrap();
mock.assert_request("eth_blockNumber", ()).unwrap();
mock.assert_request("eth_blockNumber", ()).unwrap_err();
}
provider.get_block_number().await.unwrap_err();
#[tokio::test]
async fn build_declarative_middleware_stack() {
let (provider, mock) = Provider::mocked();
let signer = LocalWallet::new(&mut thread_rng());
let address = signer.address();
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
let gas_oracle = EthGasStation::new(None);
let provider = provider
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
.gas_oracle(gas_oracle)
.with_signer(signer)
.nonce_manager(address);
// push a response
mock.push(U64::from(12u64)).unwrap();
let block: U64 = provider.get_block_number().await.unwrap();
assert_eq!(block.as_u64(), 12);
provider.get_block_number().await.unwrap_err();
// 2 calls were made
mock.assert_request("eth_blockNumber", ()).unwrap();
mock.assert_request("eth_blockNumber", ()).unwrap();
mock.assert_request("eth_blockNumber", ()).unwrap_err();
}
// 2 calls were made
mock.assert_request("eth_blockNumber", ()).unwrap();
mock.assert_request("eth_blockNumber", ()).unwrap();
mock.assert_request("eth_blockNumber", ()).unwrap_err();
}
#[tokio::test]
async fn build_declarative_middleware_stack() {
let (provider, mock) = Provider::mocked();
let signer = LocalWallet::new(&mut thread_rng());
let address = signer.address();
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
let gas_oracle = GasNow::new();
let provider = provider
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
.gas_oracle(gas_oracle)
.with_signer(signer)
.nonce_manager(address);
// push a response
mock.push(U64::from(12u64)).unwrap();
let block: U64 = provider.get_block_number().await.unwrap();
assert_eq!(block.as_u64(), 12);
provider.get_block_number().await.unwrap_err();
// 2 calls were made
mock.assert_request("eth_blockNumber", ()).unwrap();
mock.assert_request("eth_blockNumber", ()).unwrap();
mock.assert_request("eth_blockNumber", ()).unwrap_err();
}

View File

@ -1,4 +1,5 @@
#![cfg(not(target_arch = "wasm32"))]
use ethers_core::types::*;
use ethers_middleware::{
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},

View File

@ -1,16 +1,14 @@
#![cfg(not(target_arch = "wasm32"))]
use std::convert::TryFrom;
use async_trait::async_trait;
use ethers_core::{types::*, utils::Anvil};
use ethers_middleware::gas_oracle::{
EthGasStation, Etherchain, Etherscan, GasCategory, GasOracle, GasOracleError,
GasOracleMiddleware,
BlockNative, Etherchain, Etherscan, GasCategory, GasNow, GasOracle, GasOracleError,
GasOracleMiddleware, Polygon, ProviderOracle, Result,
};
use ethers_providers::{Http, Middleware, Provider};
use serial_test::serial;
use std::convert::TryFrom;
#[derive(Debug)]
struct FakeGasOracle {
@ -20,17 +18,18 @@ struct FakeGasOracle {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for FakeGasOracle {
async fn fetch(&self) -> Result<U256, GasOracleError> {
async fn fetch(&self) -> Result<U256> {
Ok(self.gas_price)
}
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
Err(GasOracleError::Eip1559EstimationNotSupported)
}
}
#[tokio::test]
async fn using_gas_oracle() {
#[serial]
async fn provider_using_gas_oracle() {
let anvil = Anvil::new().spawn();
let from = anvil.addresses()[0];
@ -38,11 +37,11 @@ async fn using_gas_oracle() {
// connect to the network
let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
// initial base fee
let base_fee = 1_000_000_000u64;
// assign a gas oracle to use
let gas_oracle = FakeGasOracle { gas_price: (base_fee + 1337).into() };
let expected_gas_price = gas_oracle.fetch().await.unwrap();
let expected_gas_price = U256::from(1234567890_u64);
let gas_oracle = FakeGasOracle { gas_price: expected_gas_price };
let gas_price = gas_oracle.fetch().await.unwrap();
assert_eq!(gas_price, expected_gas_price);
let provider = GasOracleMiddleware::new(provider, gas_oracle);
@ -55,35 +54,70 @@ async fn using_gas_oracle() {
}
#[tokio::test]
async fn eth_gas_station() {
// initialize and fetch gas estimates from EthGasStation
let eth_gas_station_oracle = EthGasStation::default();
let data = eth_gas_station_oracle.fetch().await;
data.unwrap();
#[serial]
async fn provider_oracle() {
// spawn anvil and connect to it
let anvil = Anvil::new().spawn();
let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
// assert that provider.get_gas_price() and oracle.fetch() return the same value
let expected_gas_price = provider.get_gas_price().await.unwrap();
let provider_oracle = ProviderOracle::new(provider);
let gas = provider_oracle.fetch().await.unwrap();
assert_eq!(gas, expected_gas_price);
}
#[tokio::test]
async fn blocknative() {
let gas_now_oracle = BlockNative::default();
let gas_price = gas_now_oracle.fetch().await.unwrap();
assert!(gas_price > U256::zero());
}
#[tokio::test]
#[ignore = "ETHGasStation is shutting down: https://twitter.com/ETHGasStation/status/1597341610777317376"]
#[allow(deprecated)]
async fn eth_gas_station() {
let eth_gas_station_oracle = ethers_middleware::gas_oracle::EthGasStation::default();
let gas_price = eth_gas_station_oracle.fetch().await.unwrap();
assert!(gas_price > U256::zero());
}
#[tokio::test]
#[ignore = "Etherchain / beaconcha.in's `gasPriceOracle` API currently returns 404: https://www.etherchain.org/api/gasPriceOracle"]
async fn etherchain() {
let etherchain_oracle = Etherchain::default();
let gas_price = etherchain_oracle.fetch().await.unwrap();
assert!(gas_price > U256::zero());
}
#[tokio::test]
#[serial]
async fn etherscan() {
let etherscan_client = ethers_etherscan::Client::new_from_env(Chain::Mainnet).unwrap();
// initialize and fetch gas estimates from Etherscan
// since etherscan does not support `fastest` category, we expect an error
let etherscan_oracle = Etherscan::new(etherscan_client.clone()).category(GasCategory::Fastest);
let data = etherscan_oracle.fetch().await;
data.unwrap_err();
let error = etherscan_oracle.fetch().await.unwrap_err();
assert!(matches!(error, GasOracleError::GasCategoryNotSupported));
// but fetching the `standard` gas price should work fine
let etherscan_oracle = Etherscan::new(etherscan_client).category(GasCategory::SafeLow);
let data = etherscan_oracle.fetch().await;
data.unwrap();
let gas_price = etherscan_oracle.fetch().await.unwrap();
assert!(gas_price > U256::zero());
}
#[tokio::test]
async fn etherchain() {
// initialize and fetch gas estimates from Etherchain
let etherchain_oracle = Etherchain::default().category(GasCategory::Fast);
let data = etherchain_oracle.fetch().await;
data.unwrap();
async fn gas_now() {
let gas_now_oracle = GasNow::default();
let gas_price = gas_now_oracle.fetch().await.unwrap();
assert!(gas_price > U256::zero());
}
#[tokio::test]
async fn polygon() {
let polygon_oracle = Polygon::default();
let gas_price = polygon_oracle.fetch().await.unwrap();
assert!(gas_price > U256::zero());
}

View File

@ -1,18 +1,18 @@
#![cfg(not(target_arch = "wasm32"))]
#[tokio::test]
#[cfg(not(feature = "celo"))]
async fn nonce_manager() {
use ethers_core::types::*;
use ethers_middleware::{nonce_manager::NonceManagerMiddleware, signer::SignerMiddleware};
use ethers_providers::Middleware;
use ethers_signers::{LocalWallet, Signer};
use std::time::Duration;
#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
use ethers_core::types::*;
use ethers_middleware::{nonce_manager::NonceManagerMiddleware, signer::SignerMiddleware};
use ethers_providers::Middleware;
use ethers_signers::{LocalWallet, Signer};
use std::time::Duration;
#[tokio::test]
async fn nonce_manager() {
let provider = ethers_providers::GOERLI.provider().interval(Duration::from_millis(2000u64));
let chain_id = provider.get_chainid().await.unwrap().as_u64();
let wallet = std::env::var("GOERLI_PRIVATE_KEY")
.unwrap()
.expect("GOERLI_PRIVATE_KEY is not defined")
.parse::<LocalWallet>()
.unwrap()
.with_chain_id(chain_id);

View File

@ -1,14 +1,22 @@
#![allow(unused)]
use ethers_providers::{Http, JsonRpcClient, Middleware, Provider, GOERLI};
use ethers_contract::ContractFactory;
use ethers_core::{
types::{BlockNumber, TransactionRequest},
utils::parse_units,
abi::Abi,
types::*,
utils::{parse_ether, parse_units, Anvil},
};
use ethers_middleware::signer::SignerMiddleware;
use ethers_providers::{Http, JsonRpcClient, Middleware, Provider, GOERLI};
use ethers_signers::{coins_bip39::English, LocalWallet, MnemonicBuilder, Signer};
use ethers_solc::Solc;
use once_cell::sync::Lazy;
use std::{convert::TryFrom, iter::Cycle, sync::atomic::AtomicU8, time::Duration};
use std::{
convert::TryFrom,
iter::Cycle,
sync::{atomic::AtomicU8, Arc},
time::Duration,
};
static WALLETS: Lazy<TestWallets> = Lazy::new(|| {
TestWallets {
@ -22,8 +30,6 @@ static WALLETS: Lazy<TestWallets> = Lazy::new(|| {
#[tokio::test]
#[cfg(not(feature = "celo"))]
async fn send_eth() {
use ethers_core::utils::Anvil;
let anvil = Anvil::new().spawn();
// this private key belongs to the above mnemonic
@ -95,10 +101,6 @@ async fn pending_txs_with_confirmations_testnet() {
generic_pending_txs_test(provider, address).await;
}
#[cfg(not(feature = "celo"))]
use ethers_core::types::{Address, Eip1559TransactionRequest};
use ethers_core::utils::parse_ether;
// different keys to avoid nonce errors
#[tokio::test]
#[cfg(not(feature = "celo"))]
@ -195,8 +197,6 @@ async fn test_send_transaction() {
#[tokio::test]
#[cfg(not(feature = "celo"))]
async fn send_transaction_handles_tx_from_field() {
use ethers_core::utils::Anvil;
// launch anvil
let anvil = Anvil::new().spawn();
@ -240,14 +240,6 @@ async fn send_transaction_handles_tx_from_field() {
#[tokio::test]
#[cfg(feature = "celo")]
async fn deploy_and_call_contract() {
use ethers_contract::ContractFactory;
use ethers_core::{
abi::Abi,
types::{BlockNumber, Bytes, H256, U256},
};
use ethers_solc::Solc;
use std::sync::Arc;
// compiles the given contract and returns the ABI and Bytecode
fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
let path = format!("./tests/solidity-contracts/{path}");

View File

@ -1,95 +1,93 @@
#![cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "celo"))]
mod tests {
use ethers_core::{rand::thread_rng, types::TransactionRequest, utils::Anvil};
use ethers_middleware::{
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
gas_oracle::{EthGasStation, GasCategory, GasOracleMiddleware},
nonce_manager::NonceManagerMiddleware,
signer::SignerMiddleware,
};
use ethers_providers::{Http, Middleware, Provider};
use ethers_signers::{LocalWallet, Signer};
use std::convert::TryFrom;
#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
#[tokio::test]
async fn mock_with_middleware() {
let (provider, mock) = Provider::mocked();
use ethers_core::{rand::thread_rng, types::TransactionRequest, utils::Anvil};
use ethers_middleware::{
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
gas_oracle::{GasCategory, GasNow, GasOracleMiddleware},
nonce_manager::NonceManagerMiddleware,
signer::SignerMiddleware,
};
use ethers_providers::{Http, Middleware, Provider};
use ethers_signers::{LocalWallet, Signer};
use std::convert::TryFrom;
// add a bunch of middlewares
let gas_oracle = EthGasStation::new(None).category(GasCategory::SafeLow);
let signer = LocalWallet::new(&mut thread_rng());
let address = signer.address();
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
let provider = GasOracleMiddleware::new(provider, gas_oracle);
let provider = SignerMiddleware::new(provider, signer);
let provider = NonceManagerMiddleware::new(provider, address);
#[tokio::test]
async fn mock_with_middleware() {
let (provider, mock) = Provider::mocked();
// push a response
use ethers_core::types::U64;
mock.push(U64::from(12u64)).unwrap();
let blk = provider.get_block_number().await.unwrap();
assert_eq!(blk.as_u64(), 12);
// add a bunch of middlewares
let gas_oracle = GasNow::new().category(GasCategory::SafeLow);
let signer = LocalWallet::new(&mut thread_rng());
let address = signer.address();
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
let provider = GasOracleMiddleware::new(provider, gas_oracle);
let provider = SignerMiddleware::new(provider, signer);
let provider = NonceManagerMiddleware::new(provider, address);
// now that the response is gone, there's nothing left
// TODO: This returns:
// MiddlewareError(
// MiddlewareError(
// MiddlewareError(
// MiddlewareError(
// JsonRpcClientError(EmptyResponses)
// ))))
// Can we flatten it in any way? Maybe inherent to the middleware
// infrastructure
provider.get_block_number().await.unwrap_err();
// push a response
use ethers_core::types::U64;
mock.push(U64::from(12u64)).unwrap();
let blk = provider.get_block_number().await.unwrap();
assert_eq!(blk.as_u64(), 12);
// 2 calls were made
mock.assert_request("eth_blockNumber", ()).unwrap();
mock.assert_request("eth_blockNumber", ()).unwrap();
mock.assert_request("eth_blockNumber", ()).unwrap_err();
}
// now that the response is gone, there's nothing left
// TODO: This returns:
// MiddlewareError(
// MiddlewareError(
// MiddlewareError(
// MiddlewareError(
// JsonRpcClientError(EmptyResponses)
// ))))
// Can we flatten it in any way? Maybe inherent to the middleware
// infrastructure
provider.get_block_number().await.unwrap_err();
#[tokio::test]
async fn can_stack_middlewares() {
let anvil = Anvil::new().block_time(5u64).spawn();
let gas_oracle = EthGasStation::new(None).category(GasCategory::SafeLow);
let signer: LocalWallet = anvil.keys()[0].clone().into();
let address = signer.address();
// the base provider
let provider = Arc::new(Provider::<Http>::try_from(anvil.endpoint()).unwrap());
let chain_id = provider.get_chainid().await.unwrap().as_u64();
let signer = signer.with_chain_id(chain_id);
// the Gas Price escalator middleware is the first middleware above the provider,
// so that it receives the transaction last, after all the other middleware
// have modified it accordingly
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
// The gas price middleware MUST be below the signing middleware for things to work
let provider = GasOracleMiddleware::new(provider, gas_oracle);
// The signing middleware signs txs
use std::sync::Arc;
let provider = Arc::new(SignerMiddleware::new(provider, signer));
// The nonce manager middleware MUST be above the signing middleware so that it overrides
// the nonce and the signer does not make any eth_getTransaction count calls
let provider = NonceManagerMiddleware::new(provider, address);
let tx = TransactionRequest::new();
let mut pending_txs = Vec::new();
for _ in 0..10 {
let pending = provider.send_transaction(tx.clone(), None).await.unwrap();
let hash = *pending;
let gas_price = provider.get_transaction(hash).await.unwrap().unwrap().gas_price;
dbg!(gas_price);
pending_txs.push(pending);
}
let receipts = futures_util::future::join_all(pending_txs);
dbg!(receipts.await);
}
// 2 calls were made
mock.assert_request("eth_blockNumber", ()).unwrap();
mock.assert_request("eth_blockNumber", ()).unwrap();
mock.assert_request("eth_blockNumber", ()).unwrap_err();
}
#[tokio::test]
async fn can_stack_middlewares() {
let anvil = Anvil::new().block_time(5u64).spawn();
let gas_oracle = GasNow::new().category(GasCategory::SafeLow);
let signer: LocalWallet = anvil.keys()[0].clone().into();
let address = signer.address();
// the base provider
let provider = Arc::new(Provider::<Http>::try_from(anvil.endpoint()).unwrap());
let chain_id = provider.get_chainid().await.unwrap().as_u64();
let signer = signer.with_chain_id(chain_id);
// the Gas Price escalator middleware is the first middleware above the provider,
// so that it receives the transaction last, after all the other middleware
// have modified it accordingly
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
// The gas price middleware MUST be below the signing middleware for things to work
let provider = GasOracleMiddleware::new(provider, gas_oracle);
// The signing middleware signs txs
use std::sync::Arc;
let provider = Arc::new(SignerMiddleware::new(provider, signer));
// The nonce manager middleware MUST be above the signing middleware so that it overrides
// the nonce and the signer does not make any eth_getTransaction count calls
let provider = NonceManagerMiddleware::new(provider, address);
let tx = TransactionRequest::new();
let mut pending_txs = Vec::new();
for _ in 0..10 {
let pending = provider.send_transaction(tx.clone(), None).await.unwrap();
let hash = *pending;
let gas_price = provider.get_transaction(hash).await.unwrap().unwrap().gas_price;
dbg!(gas_price);
pending_txs.push(pending);
}
let receipts = futures_util::future::join_all(pending_txs);
dbg!(receipts.await);
}

View File

@ -1,5 +1,5 @@
#![cfg(not(target_arch = "wasm32"))]
#![allow(unused)]
#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
use ethers_contract::{BaseContract, ContractFactory};
use ethers_core::{abi::Abi, types::*, utils::Anvil};
use ethers_middleware::{
@ -24,7 +24,6 @@ fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
}
#[tokio::test]
#[cfg(not(feature = "celo"))]
async fn ds_proxy_transformer() {
// randomness
let mut rng = rand::thread_rng();
@ -83,7 +82,6 @@ async fn ds_proxy_transformer() {
}
#[tokio::test]
#[cfg(not(feature = "celo"))]
async fn ds_proxy_code() {
// randomness
let mut rng = rand::thread_rng();