diff --git a/ethers-etherscan/src/blocks.rs b/ethers-etherscan/src/blocks.rs new file mode 100644 index 000000000..f009e32ba --- /dev/null +++ b/ethers-etherscan/src/blocks.rs @@ -0,0 +1,47 @@ +use crate::{Client, EtherscanError, Response, Result}; +use ethers_core::types::BlockNumber; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct BlockNumberByTimestamp { + pub timestamp: u64, + pub block_number: BlockNumber, +} + +impl Client { + /// Returns either (1) the oldest block since a particular timestamp occurred or (2) the newest + /// block that occurred prior to that timestamp + /// + /// # Examples + /// + /// ```no_run + /// # async fn foo(client: ethers_etherscan::Client) -> Result<(), Box> { + /// // The newest block that occurred prior to 1 January 2020 + /// let block_number_before = client.get_block_by_timestamp(1577836800, "before"); + /// // The oldest block that occurred after 1 January 2020 + /// let block_number_after = client.get_block_by_timestamp(1577836800, "after"); + /// # Ok(()) } + /// ``` + pub async fn get_block_by_timestamp( + &self, + timestamp: u64, + closest: &str, + ) -> Result { + let query = self.create_query( + "block", + "getblocknobytime", + HashMap::from([("timestamp", timestamp.to_string()), ("closest", closest.to_string())]), + ); + let response: Response = self.get_json(&query).await?; + + match response.status.as_str() { + "0" => Err(EtherscanError::BlockNumberByTimestampFailed), + "1" => Ok(BlockNumberByTimestamp { + timestamp, + block_number: response.result.parse::().unwrap(), + }), + err => Err(EtherscanError::BadStatusCode(err.to_string())), + } + } +} diff --git a/ethers-etherscan/src/errors.rs b/ethers-etherscan/src/errors.rs index 5f71f52d9..d3208e7a3 100644 --- a/ethers-etherscan/src/errors.rs +++ b/ethers-etherscan/src/errors.rs @@ -9,6 +9,8 @@ pub enum EtherscanError { ExecutionFailed(String), #[error("Balance failed")] BalanceFailed, + #[error("Block by timestamp failed")] + BlockNumberByTimestampFailed, #[error("Transaction receipt failed")] TransactionReceiptFailed, #[error("Gas estimation failed")] diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs index c1ea855e4..12827955b 100644 --- a/ethers-etherscan/src/lib.rs +++ b/ethers-etherscan/src/lib.rs @@ -20,6 +20,7 @@ use std::{ use tracing::{error, trace}; pub mod account; +pub mod blocks; pub mod contract; pub mod errors; pub mod gas; diff --git a/ethers-etherscan/tests/it/blocks.rs b/ethers-etherscan/tests/it/blocks.rs new file mode 100644 index 000000000..9c7ee6a24 --- /dev/null +++ b/ethers-etherscan/tests/it/blocks.rs @@ -0,0 +1,28 @@ +use crate::*; +use ethers_core::types::BlockNumber; +use serial_test::serial; + +#[tokio::test] +#[serial] +async fn check_get_block_by_timestamp_before() { + run_with_client(Chain::Mainnet, |client| async move { + let block_no = client.get_block_by_timestamp(1577836800, "before").await; + assert!(block_no.is_ok()); + + let block_no = block_no.unwrap().block_number; + assert_eq!(block_no, "9193265".parse::().unwrap()); + }) + .await +} + +#[tokio::test] +#[serial] +async fn check_get_block_by_timestamp_after() { + run_with_client(Chain::Mainnet, |client| async move { + let block_no = client.get_block_by_timestamp(1577836800, "after").await; + + let block_no = block_no.unwrap().block_number; + assert_eq!(block_no, "9193266".parse::().unwrap()); + }) + .await +} diff --git a/ethers-etherscan/tests/it/main.rs b/ethers-etherscan/tests/it/main.rs index e0548705e..0eac9aa17 100644 --- a/ethers-etherscan/tests/it/main.rs +++ b/ethers-etherscan/tests/it/main.rs @@ -10,6 +10,7 @@ use std::{ }; mod account; +mod blocks; mod contract; mod gas; mod transaction;