use crate::gas_oracle::{GasOracle, GasOracleError}; use async_trait::async_trait; use ethers_core::types::U256; use futures_locks::RwLock; use std::{ fmt::Debug, future::Future, time::{Duration, Instant}, }; #[derive(Debug)] pub struct Cache { inner: T, validity: Duration, fee: Cached, eip1559: Cached<(U256, U256)>, } #[derive(Default, Debug)] struct Cached(RwLock>); impl Cached { async fn get(&self, validity: Duration, fetch: F) -> Result where F: FnOnce() -> Fut, Fut: Future>, { // Try with a read lock { let lock = self.0.read().await; if let Some((last_fetch, value)) = lock.as_ref() { if Instant::now().duration_since(*last_fetch) < validity { return Ok(value.clone()) } } } // Acquire a write lock { let mut lock = self.0.write().await; // Check again, a concurrent thread may have raced us to the write. if let Some((last_fetch, value)) = lock.as_ref() { if Instant::now().duration_since(*last_fetch) < validity { return Ok(value.clone()) } } // Set a fresh value let value = fetch().await?; *lock = Some((Instant::now(), value.clone())); Ok(value) } } } impl Cache { 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 GasOracle for Cache { async fn fetch(&self) -> Result { 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 } }