diff --git a/Cargo.lock b/Cargo.lock index c126dbd1c..9fb231c3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1245,6 +1245,7 @@ dependencies = [ "bincode", "bytes", "cargo_metadata", + "chrono", "convert_case", "elliptic-curve", "ethabi", diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml index 4476237f8..2d3d61af0 100644 --- a/ethers-core/Cargo.toml +++ b/ethers-core/Cargo.toml @@ -23,6 +23,7 @@ rand = { version = "0.8.5", default-features = false } tiny-keccak = { version = "2.0.2", default-features = false } # misc +chrono = { version = "0.4", default-features = false } serde = { version = "1.0.124", default-features = false, features = ["derive"] } serde_json = { version = "1.0.64", default-features = false } thiserror = { version = "1.0.31", default-features = false } diff --git a/ethers-core/src/types/block.rs b/ethers-core/src/types/block.rs index c3038dc84..61c9f16b9 100644 --- a/ethers-core/src/types/block.rs +++ b/ethers-core/src/types/block.rs @@ -1,9 +1,11 @@ // Taken from use crate::types::{Address, Bloom, Bytes, Transaction, TxHash, H256, U256, U64}; +use chrono::{DateTime, TimeZone, Utc}; #[cfg(not(feature = "celo"))] use core::cmp::Ordering; use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; use std::str::FromStr; +use thiserror::Error; /// The block type returned from RPC calls. /// This is generic over a `TX` type which will be either the hash or the full transaction, @@ -91,6 +93,18 @@ pub struct Block { pub epoch_snark_data: Option, } +/// Error returned by [`Block::time`]. +#[derive(Clone, Copy, Debug, Error)] +pub enum TimeError { + /// Timestamp is zero. + #[error("timestamp is zero")] + TimestampZero, + + /// Timestamp is too large for [`DateTime`]. + #[error("timestamp is too large")] + TimestampOverflow, +} + // ref #[cfg(not(feature = "celo"))] pub const ELASTICITY_MULTIPLIER: U256 = U256([2u64, 0, 0, 0]); @@ -135,6 +149,26 @@ impl Block { Ordering::Equal => self.base_fee_per_gas, } } + + /// Parse [`Self::timestamp`] into a [`DateTime`]. + /// + /// # Errors + /// + /// * [`TimeError::TimestampZero`] if the timestamp is zero, or + /// * [`TimeError::TimestampOverflow`] if the timestamp is too large to be represented as a + /// [`DateTime`]. + pub fn time(&self) -> Result, TimeError> { + if self.timestamp.is_zero() { + return Err(TimeError::TimestampZero) + } + if self.timestamp.bits() > 63 { + return Err(TimeError::TimestampOverflow) + } + // Casting to i64 is safe because the timestamp is guaranteed to be less than 2^63. + // TODO: It would be nice if there was `TryInto for U256`. + let secs = self.timestamp.as_u64() as i64; + Ok(Utc.timestamp(secs, 0)) + } } impl Block { diff --git a/ethers-core/src/types/mod.rs b/ethers-core/src/types/mod.rs index b19273fff..994cc8edf 100644 --- a/ethers-core/src/types/mod.rs +++ b/ethers-core/src/types/mod.rs @@ -31,7 +31,7 @@ mod bytes; pub use self::bytes::{deserialize_bytes, serialize_bytes, Bytes, ParseBytesError}; mod block; -pub use block::{Block, BlockId, BlockNumber}; +pub use block::{Block, BlockId, BlockNumber, TimeError}; #[cfg(feature = "celo")] pub use block::Randomness;