1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
pub mod blocknative;
pub use blocknative::BlockNative;

pub mod eth_gas_station;
#[allow(deprecated)]
pub use eth_gas_station::EthGasStation;

pub mod etherchain;
pub use etherchain::Etherchain;

#[cfg(feature = "etherscan")]
pub mod etherscan;
#[cfg(feature = "etherscan")]
pub use etherscan::Etherscan;

pub mod middleware;
pub use middleware::{GasOracleMiddleware, MiddlewareError};

pub mod median;
pub use median::Median;

pub mod cache;
pub use cache::Cache;

pub mod polygon;
pub use polygon::Polygon;

pub mod gas_now;
pub use gas_now::GasNow;

pub mod provider_oracle;
pub use provider_oracle::ProviderOracle;

use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers_core::types::U256;
use reqwest::Error as ReqwestError;
use std::{error::Error, fmt::Debug};
use thiserror::Error;

pub(crate) const GWEI_TO_WEI: u64 = 1_000_000_000;
pub(crate) const GWEI_TO_WEI_U256: U256 = U256([GWEI_TO_WEI, 0, 0, 0]);

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,
}

/// Error thrown by a [`GasOracle`].
#[derive(Debug, Error)]
pub enum GasOracleError {
    /// An internal error in the HTTP request made from the underlying
    /// gas oracle
    #[error(transparent)]
    HttpClientError(#[from] ReqwestError),

    /// An error decoding JSON response from gas oracle
    #[error(transparent)]
    SerdeJsonError(#[from] serde_json::Error),

    /// An error with oracle response type
    #[error("invalid oracle response")]
    InvalidResponse,

    /// An internal error in the Etherscan client request made from the underlying
    /// gas oracle
    #[error(transparent)]
    #[cfg(feature = "etherscan")]
    EtherscanError(#[from] ethers_etherscan::errors::EtherscanError),

    /// An internal error thrown when the required gas category is not
    /// supported by the gas oracle API
    #[error("gas category not supported")]
    GasCategoryNotSupported,

    #[error("EIP-1559 gas estimation not supported")]
    Eip1559EstimationNotSupported,

    #[error("None of the oracles returned a value")]
    NoValues,

    #[error("Chain is not supported by the oracle")]
    UnsupportedChain,

    /// Error thrown when the provider failed.
    #[error("Provider error: {0}")]
    ProviderError(#[from] Box<dyn Error + Send + Sync>),
    #[error("Failed to parse gas values: {0}")]
    ConversionError(#[from] ethers_core::utils::ConversionError),
}

/// An Ethereum gas price oracle.
///
/// # 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 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 + Debug {
    /// Makes an asynchronous HTTP query to the underlying [`GasOracle`] to fetch the current gas
    /// price estimate.
    ///
    /// # 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 gas_price = oracle.fetch().await?;
    /// assert!(gas_price > U256::zero());
    /// # Ok(())
    /// # }
    /// ```
    async fn fetch(&self) -> Result<U256>;

    /// 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 fn from_gwei_f64(gwei: f64) -> U256 {
    U256::from((gwei * GWEI_TO_WEI as f64).ceil() as u64)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_gwei_wei_constants() {
        let as_u256: U256 = GWEI_TO_WEI.into();
        assert_eq!(as_u256, GWEI_TO_WEI_U256);
        assert_eq!(GWEI_TO_WEI_U256.as_u64(), GWEI_TO_WEI);
    }

    #[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));
    }
}