use alloy_primitives::{hex::FromHexError, ruint::ParseError, BlockHash, B256, U64};
use alloy_rlp::{bytes, Decodable, Encodable, Error as RlpError};
use core::{
fmt::{self, Debug, Display, Formatter},
num::ParseIntError,
str::FromStr,
};
#[cfg(feature = "serde")]
use serde::ser::SerializeStruct;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename = "camelCase"))]
pub struct RpcBlockHash {
pub block_hash: BlockHash,
pub require_canonical: Option<bool>,
}
impl RpcBlockHash {
#[doc(alias = "from_block_hash")]
pub const fn from_hash(block_hash: B256, require_canonical: Option<bool>) -> Self {
Self { block_hash, require_canonical }
}
}
impl From<B256> for RpcBlockHash {
fn from(value: B256) -> Self {
Self::from_hash(value, None)
}
}
impl From<RpcBlockHash> for B256 {
fn from(value: RpcBlockHash) -> Self {
value.block_hash
}
}
impl AsRef<B256> for RpcBlockHash {
fn as_ref(&self) -> &B256 {
&self.block_hash
}
}
impl Display for RpcBlockHash {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let Self { block_hash, require_canonical } = self;
if *require_canonical == Some(true) {
write!(f, "canonical ")?
}
write!(f, "hash {}", block_hash)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum BlockNumberOrTag {
#[default]
Latest,
Finalized,
Safe,
Earliest,
Pending,
Number(u64),
}
impl BlockNumberOrTag {
pub const fn as_number(&self) -> Option<u64> {
match *self {
Self::Number(num) => Some(num),
_ => None,
}
}
pub const fn is_number(&self) -> bool {
matches!(self, Self::Number(_))
}
pub const fn is_latest(&self) -> bool {
matches!(self, Self::Latest)
}
pub const fn is_finalized(&self) -> bool {
matches!(self, Self::Finalized)
}
pub const fn is_safe(&self) -> bool {
matches!(self, Self::Safe)
}
pub const fn is_pending(&self) -> bool {
matches!(self, Self::Pending)
}
pub const fn is_earliest(&self) -> bool {
matches!(self, Self::Earliest)
}
}
impl From<u64> for BlockNumberOrTag {
fn from(num: u64) -> Self {
Self::Number(num)
}
}
impl From<U64> for BlockNumberOrTag {
fn from(num: U64) -> Self {
num.to::<u64>().into()
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for BlockNumberOrTag {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match *self {
Self::Number(x) => serializer.serialize_str(&format!("0x{x:x}")),
Self::Latest => serializer.serialize_str("latest"),
Self::Finalized => serializer.serialize_str("finalized"),
Self::Safe => serializer.serialize_str("safe"),
Self::Earliest => serializer.serialize_str("earliest"),
Self::Pending => serializer.serialize_str("pending"),
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for BlockNumberOrTag {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = alloc::string::String::deserialize(deserializer)?.to_lowercase();
s.parse().map_err(serde::de::Error::custom)
}
}
impl FromStr for BlockNumberOrTag {
type Err = ParseBlockNumberError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"latest" => Self::Latest,
"finalized" => Self::Finalized,
"safe" => Self::Safe,
"earliest" => Self::Earliest,
"pending" => Self::Pending,
_number => {
if let Some(hex_val) = s.strip_prefix("0x") {
u64::from_str_radix(hex_val, 16)?.into()
} else {
return Err(HexStringMissingPrefixError::default().into());
}
}
})
}
}
impl fmt::Display for BlockNumberOrTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Number(x) => write!(f, "0x{x:x}"),
Self::Latest => f.write_str("latest"),
Self::Finalized => f.write_str("finalized"),
Self::Safe => f.write_str("safe"),
Self::Earliest => f.write_str("earliest"),
Self::Pending => f.write_str("pending"),
}
}
}
#[derive(Debug)]
pub enum ParseBlockNumberError {
ParseIntErr(ParseIntError),
ParseErr(ParseError),
MissingPrefix(HexStringMissingPrefixError),
}
#[cfg(feature = "std")]
impl std::error::Error for ParseBlockNumberError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::ParseIntErr(err) => std::error::Error::source(err),
Self::ParseErr(err) => std::error::Error::source(err),
Self::MissingPrefix(err) => std::error::Error::source(err),
}
}
}
impl Display for ParseBlockNumberError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::ParseIntErr(err) => write!(f, "{err}"),
Self::ParseErr(err) => write!(f, "{err}"),
Self::MissingPrefix(err) => write!(f, "{err}"),
}
}
}
impl From<ParseIntError> for ParseBlockNumberError {
fn from(err: ParseIntError) -> Self {
Self::ParseIntErr(err)
}
}
impl From<ParseError> for ParseBlockNumberError {
fn from(err: ParseError) -> Self {
Self::ParseErr(err)
}
}
impl From<HexStringMissingPrefixError> for ParseBlockNumberError {
fn from(err: HexStringMissingPrefixError) -> Self {
Self::MissingPrefix(err)
}
}
#[derive(Clone, Copy, Debug, Default)]
#[non_exhaustive]
pub struct HexStringMissingPrefixError;
impl Display for HexStringMissingPrefixError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("hex string without 0x prefix")
}
}
#[cfg(feature = "std")]
impl std::error::Error for HexStringMissingPrefixError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BlockId {
Hash(RpcBlockHash),
Number(BlockNumberOrTag),
}
impl BlockId {
pub const fn as_block_hash(&self) -> Option<BlockHash> {
match self {
Self::Hash(hash) => Some(hash.block_hash),
Self::Number(_) => None,
}
}
pub const fn as_u64(&self) -> Option<u64> {
match self {
Self::Number(x) => x.as_number(),
_ => None,
}
}
pub const fn is_latest(&self) -> bool {
matches!(self, Self::Number(BlockNumberOrTag::Latest))
}
pub const fn is_pending(&self) -> bool {
matches!(self, Self::Number(BlockNumberOrTag::Pending))
}
pub const fn is_safe(&self) -> bool {
matches!(self, Self::Number(BlockNumberOrTag::Safe))
}
pub const fn is_finalized(&self) -> bool {
matches!(self, Self::Number(BlockNumberOrTag::Finalized))
}
pub const fn is_earliest(&self) -> bool {
matches!(self, Self::Number(BlockNumberOrTag::Earliest))
}
pub const fn is_number(&self) -> bool {
matches!(self, Self::Number(BlockNumberOrTag::Number(_)))
}
pub const fn is_hash(&self) -> bool {
matches!(self, Self::Hash(_))
}
pub const fn pending() -> Self {
Self::Number(BlockNumberOrTag::Pending)
}
pub const fn latest() -> Self {
Self::Number(BlockNumberOrTag::Latest)
}
pub const fn earliest() -> Self {
Self::Number(BlockNumberOrTag::Earliest)
}
pub const fn finalized() -> Self {
Self::Number(BlockNumberOrTag::Finalized)
}
pub const fn safe() -> Self {
Self::Number(BlockNumberOrTag::Safe)
}
pub const fn number(num: u64) -> Self {
Self::Number(BlockNumberOrTag::Number(num))
}
pub const fn hash(block_hash: BlockHash) -> Self {
Self::Hash(RpcBlockHash { block_hash, require_canonical: None })
}
pub const fn hash_canonical(block_hash: BlockHash) -> Self {
Self::Hash(RpcBlockHash { block_hash, require_canonical: Some(true) })
}
}
impl Default for BlockId {
fn default() -> Self {
BlockNumberOrTag::Latest.into()
}
}
impl From<u64> for BlockId {
fn from(num: u64) -> Self {
BlockNumberOrTag::Number(num).into()
}
}
impl From<U64> for BlockId {
fn from(value: U64) -> Self {
value.to::<u64>().into()
}
}
impl From<BlockNumberOrTag> for BlockId {
fn from(num: BlockNumberOrTag) -> Self {
Self::Number(num)
}
}
impl From<HashOrNumber> for BlockId {
fn from(block: HashOrNumber) -> Self {
match block {
HashOrNumber::Hash(hash) => hash.into(),
HashOrNumber::Number(num) => num.into(),
}
}
}
impl From<B256> for BlockId {
fn from(block_hash: B256) -> Self {
RpcBlockHash { block_hash, require_canonical: None }.into()
}
}
impl From<(B256, Option<bool>)> for BlockId {
fn from(hash_can: (B256, Option<bool>)) -> Self {
RpcBlockHash { block_hash: hash_can.0, require_canonical: hash_can.1 }.into()
}
}
impl From<RpcBlockHash> for BlockId {
fn from(value: RpcBlockHash) -> Self {
Self::Hash(value)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for BlockId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Hash(RpcBlockHash { block_hash, require_canonical }) => {
let mut s = serializer.serialize_struct("BlockIdEip1898", 1)?;
s.serialize_field("blockHash", block_hash)?;
if let Some(require_canonical) = require_canonical {
s.serialize_field("requireCanonical", require_canonical)?;
}
s.end()
}
Self::Number(num) => num.serialize(serializer),
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for BlockId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct BlockIdVisitor;
impl<'de> serde::de::Visitor<'de> for BlockIdVisitor {
type Value = BlockId;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("Block identifier following EIP-1898")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v.len() == 66 {
Ok(v.parse::<B256>().map_err(serde::de::Error::custom)?.into())
} else {
Ok(BlockId::Number(v.parse().map_err(serde::de::Error::custom)?))
}
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut number = None;
let mut block_hash = None;
let mut require_canonical = None;
while let Some(key) = map.next_key::<alloc::string::String>()? {
match key.as_str() {
"blockNumber" => {
if number.is_some() || block_hash.is_some() {
return Err(serde::de::Error::duplicate_field("blockNumber"));
}
if require_canonical.is_some() {
return Err(serde::de::Error::custom(
"Non-valid require_canonical field",
));
}
number = Some(map.next_value::<BlockNumberOrTag>()?)
}
"blockHash" => {
if number.is_some() || block_hash.is_some() {
return Err(serde::de::Error::duplicate_field("blockHash"));
}
block_hash = Some(map.next_value::<B256>()?);
}
"requireCanonical" => {
if number.is_some() || require_canonical.is_some() {
return Err(serde::de::Error::duplicate_field("requireCanonical"));
}
require_canonical = Some(map.next_value::<bool>()?)
}
key => {
return Err(serde::de::Error::unknown_field(
key,
&["blockNumber", "blockHash", "requireCanonical"],
))
}
}
}
#[allow(clippy::option_if_let_else)]
if let Some(number) = number {
Ok(number.into())
} else if let Some(block_hash) = block_hash {
Ok((block_hash, require_canonical).into())
} else {
Err(serde::de::Error::custom(
"Expected `blockNumber` or `blockHash` with `requireCanonical` optionally",
))
}
}
}
deserializer.deserialize_any(BlockIdVisitor)
}
}
impl Display for BlockId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Hash(hash) => write!(f, "{}", hash),
Self::Number(num) => {
if num.is_number() {
return write!(f, "number {}", num);
}
write!(f, "{}", num)
}
}
}
}
#[derive(Debug)]
pub enum ParseBlockIdError {
ParseIntError(ParseIntError),
ParseError(ParseError),
FromHexError(FromHexError),
}
impl Display for ParseBlockIdError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::ParseIntError(err) => write!(f, "{err}"),
Self::ParseError(err) => write!(f, "{err}"),
Self::FromHexError(err) => write!(f, "{err}"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseBlockIdError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::ParseIntError(err) => std::error::Error::source(err),
Self::FromHexError(err) => std::error::Error::source(err),
Self::ParseError(err) => std::error::Error::source(err),
}
}
}
impl From<ParseIntError> for ParseBlockIdError {
fn from(err: ParseIntError) -> Self {
Self::ParseIntError(err)
}
}
impl From<FromHexError> for ParseBlockIdError {
fn from(err: FromHexError) -> Self {
Self::FromHexError(err)
}
}
impl FromStr for BlockId {
type Err = ParseBlockIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("0x") {
return match s.len() {
66 => B256::from_str(s).map(Into::into).map_err(ParseBlockIdError::FromHexError),
_ => U64::from_str(s).map(Into::into).map_err(ParseBlockIdError::ParseError),
};
}
match s {
"latest" | "finalized" | "safe" | "earliest" | "pending" => {
Ok(BlockNumberOrTag::from_str(s).unwrap().into())
}
_ => s.parse::<u64>().map_err(ParseBlockIdError::ParseIntError).map(Into::into),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NumHash {
pub number: u64,
pub hash: B256,
}
pub type ForkBlock = NumHash;
pub type BlockNumHash = NumHash;
impl NumHash {
pub const fn new(number: u64, hash: B256) -> Self {
Self { number, hash }
}
pub const fn into_components(self) -> (u64, B256) {
(self.number, self.hash)
}
pub fn matches_block_or_num(&self, block: &HashOrNumber) -> bool {
match block {
HashOrNumber::Hash(hash) => self.hash == *hash,
HashOrNumber::Number(number) => self.number == *number,
}
}
}
impl From<(u64, B256)> for NumHash {
fn from(val: (u64, B256)) -> Self {
Self { number: val.0, hash: val.1 }
}
}
impl From<(B256, u64)> for NumHash {
fn from(val: (B256, u64)) -> Self {
Self { hash: val.0, number: val.1 }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
pub enum HashOrNumber {
Hash(B256),
Number(u64),
}
pub type BlockHashOrNumber = HashOrNumber;
impl HashOrNumber {
#[inline]
pub const fn as_number(self) -> Option<u64> {
match self {
Self::Hash(_) => None,
Self::Number(num) => Some(num),
}
}
#[inline]
pub const fn as_hash(self) -> Option<B256> {
match self {
Self::Hash(hash) => Some(hash),
Self::Number(_) => None,
}
}
}
impl From<B256> for HashOrNumber {
fn from(value: B256) -> Self {
Self::Hash(value)
}
}
impl From<&B256> for HashOrNumber {
fn from(value: &B256) -> Self {
(*value).into()
}
}
impl From<u64> for HashOrNumber {
fn from(value: u64) -> Self {
Self::Number(value)
}
}
impl From<U64> for HashOrNumber {
fn from(value: U64) -> Self {
value.to::<u64>().into()
}
}
impl From<RpcBlockHash> for HashOrNumber {
fn from(value: RpcBlockHash) -> Self {
Self::Hash(value.into())
}
}
impl Encodable for HashOrNumber {
fn encode(&self, out: &mut dyn bytes::BufMut) {
match self {
Self::Hash(block_hash) => block_hash.encode(out),
Self::Number(block_number) => block_number.encode(out),
}
}
fn length(&self) -> usize {
match self {
Self::Hash(block_hash) => block_hash.length(),
Self::Number(block_number) => block_number.length(),
}
}
}
impl Decodable for HashOrNumber {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let header: u8 = *buf.first().ok_or(RlpError::InputTooShort)?;
if header == 0xa0 {
Ok(B256::decode(buf)?.into())
} else {
Ok(u64::decode(buf)?.into())
}
}
}
impl fmt::Display for HashOrNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Hash(hash) => write!(f, "{}", hash),
Self::Number(num) => write!(f, "{}", num),
}
}
}
#[derive(Debug)]
pub struct ParseBlockHashOrNumberError {
input: alloc::string::String,
parse_int_error: ParseIntError,
hex_error: alloy_primitives::hex::FromHexError,
}
impl fmt::Display for ParseBlockHashOrNumberError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"failed to parse {:?} as a number: {} or hash: {}",
self.input, self.parse_int_error, self.hex_error
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseBlockHashOrNumberError {}
impl FromStr for HashOrNumber {
type Err = ParseBlockHashOrNumberError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[cfg(not(feature = "std"))]
use alloc::string::ToString;
match u64::from_str(s) {
Ok(val) => Ok(val.into()),
Err(parse_int_error) => match B256::from_str(s) {
Ok(val) => Ok(val.into()),
Err(hex_error) => Err(ParseBlockHashOrNumberError {
input: s.to_string(),
parse_int_error,
hex_error,
}),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::{string::ToString, vec::Vec};
use alloy_primitives::b256;
const HASH: B256 = b256!("1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9");
#[test]
fn block_id_from_str() {
assert_eq!("0x0".parse::<BlockId>().unwrap(), BlockId::number(0));
assert_eq!("0x24A931".parse::<BlockId>().unwrap(), BlockId::number(2402609));
assert_eq!(
"0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
.parse::<BlockId>()
.unwrap(),
HASH.into()
);
}
#[test]
#[cfg(feature = "serde")]
fn compact_block_number_serde() {
let num: BlockNumberOrTag = 1u64.into();
let serialized = serde_json::to_string(&num).unwrap();
assert_eq!(serialized, "\"0x1\"");
}
#[test]
fn block_id_as_u64() {
assert_eq!(BlockId::number(123).as_u64(), Some(123));
assert_eq!(BlockId::number(0).as_u64(), Some(0));
assert_eq!(BlockId::earliest().as_u64(), None);
assert_eq!(BlockId::latest().as_u64(), None);
assert_eq!(BlockId::pending().as_u64(), None);
assert_eq!(BlockId::safe().as_u64(), None);
assert_eq!(BlockId::hash(BlockHash::ZERO).as_u64(), None);
assert_eq!(BlockId::hash_canonical(BlockHash::ZERO).as_u64(), None);
}
#[test]
#[cfg(feature = "serde")]
fn can_parse_eip1898_block_ids() {
let num = serde_json::json!(
{ "blockNumber": "0x0" }
);
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(id, BlockId::Number(BlockNumberOrTag::Number(0u64)));
let num = serde_json::json!(
{ "blockNumber": "pending" }
);
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(id, BlockId::Number(BlockNumberOrTag::Pending));
let num = serde_json::json!(
{ "blockNumber": "latest" }
);
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(id, BlockId::Number(BlockNumberOrTag::Latest));
let num = serde_json::json!(
{ "blockNumber": "finalized" }
);
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(id, BlockId::Number(BlockNumberOrTag::Finalized));
let num = serde_json::json!(
{ "blockNumber": "safe" }
);
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(id, BlockId::Number(BlockNumberOrTag::Safe));
let num = serde_json::json!(
{ "blockNumber": "earliest" }
);
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(id, BlockId::Number(BlockNumberOrTag::Earliest));
let num = serde_json::json!("0x0");
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(id, BlockId::Number(BlockNumberOrTag::Number(0u64)));
let num = serde_json::json!("pending");
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(id, BlockId::Number(BlockNumberOrTag::Pending));
let num = serde_json::json!("latest");
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(id, BlockId::Number(BlockNumberOrTag::Latest));
let num = serde_json::json!("finalized");
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(id, BlockId::Number(BlockNumberOrTag::Finalized));
let num = serde_json::json!("safe");
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(id, BlockId::Number(BlockNumberOrTag::Safe));
let num = serde_json::json!("earliest");
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(id, BlockId::Number(BlockNumberOrTag::Earliest));
let num = serde_json::json!(
{ "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" }
);
let id = serde_json::from_value::<BlockId>(num).unwrap();
assert_eq!(
id,
BlockId::Hash(
"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
.parse::<B256>()
.unwrap()
.into()
)
);
}
#[test]
fn display_rpc_block_hash() {
let hash = RpcBlockHash::from_hash(HASH, Some(true));
assert_eq!(
hash.to_string(),
"canonical hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
);
let hash = RpcBlockHash::from_hash(HASH, None);
assert_eq!(
hash.to_string(),
"hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
);
}
#[test]
fn display_block_id() {
let id = BlockId::hash(HASH);
assert_eq!(
id.to_string(),
"hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
);
let id = BlockId::hash_canonical(HASH);
assert_eq!(
id.to_string(),
"canonical hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
);
let id = BlockId::number(100000);
assert_eq!(id.to_string(), "number 0x186a0");
let id = BlockId::latest();
assert_eq!(id.to_string(), "latest");
let id = BlockId::safe();
assert_eq!(id.to_string(), "safe");
let id = BlockId::finalized();
assert_eq!(id.to_string(), "finalized");
let id = BlockId::earliest();
assert_eq!(id.to_string(), "earliest");
let id = BlockId::pending();
assert_eq!(id.to_string(), "pending");
}
#[test]
fn test_block_number_or_tag() {
let latest = BlockNumberOrTag::Latest;
assert_eq!(latest.as_number(), None);
assert!(latest.is_latest());
assert!(!latest.is_number());
assert!(!latest.is_finalized());
assert!(!latest.is_safe());
assert!(!latest.is_pending());
assert!(!latest.is_earliest());
let finalized = BlockNumberOrTag::Finalized;
assert_eq!(finalized.as_number(), None);
assert!(finalized.is_finalized());
assert!(!finalized.is_latest());
assert!(!finalized.is_number());
assert!(!finalized.is_safe());
assert!(!finalized.is_pending());
assert!(!finalized.is_earliest());
let safe = BlockNumberOrTag::Safe;
assert_eq!(safe.as_number(), None);
assert!(safe.is_safe());
assert!(!safe.is_latest());
assert!(!safe.is_number());
assert!(!safe.is_finalized());
assert!(!safe.is_pending());
assert!(!safe.is_earliest());
let earliest = BlockNumberOrTag::Earliest;
assert_eq!(earliest.as_number(), None);
assert!(earliest.is_earliest());
assert!(!earliest.is_latest());
assert!(!earliest.is_number());
assert!(!earliest.is_finalized());
assert!(!earliest.is_safe());
assert!(!earliest.is_pending());
let pending = BlockNumberOrTag::Pending;
assert_eq!(pending.as_number(), None);
assert!(pending.is_pending());
assert!(!pending.is_latest());
assert!(!pending.is_number());
assert!(!pending.is_finalized());
assert!(!pending.is_safe());
assert!(!pending.is_earliest());
let number = BlockNumberOrTag::Number(42);
assert_eq!(number.as_number(), Some(42));
assert!(number.is_number());
assert!(!number.is_latest());
assert!(!number.is_finalized());
assert!(!number.is_safe());
assert!(!number.is_pending());
assert!(!number.is_earliest());
}
#[test]
fn test_block_number_or_tag_from() {
let num = 100u64;
let block: BlockNumberOrTag = num.into();
assert_eq!(block, BlockNumberOrTag::Number(100));
let num = U64::from(200);
let block: BlockNumberOrTag = num.into();
assert_eq!(block, BlockNumberOrTag::Number(200));
}
#[test]
fn test_block_id() {
let hash = BlockHash::random();
let block_id_hash = BlockId::hash(hash);
assert_eq!(block_id_hash.as_block_hash(), Some(hash));
assert!(block_id_hash.is_hash());
assert!(!block_id_hash.is_number());
assert!(!block_id_hash.is_latest());
assert!(!block_id_hash.is_pending());
assert!(!block_id_hash.is_safe());
assert!(!block_id_hash.is_finalized());
assert!(!block_id_hash.is_earliest());
let block_id_number = BlockId::number(123);
assert_eq!(block_id_number.as_u64(), Some(123));
assert!(block_id_number.is_number());
assert!(!block_id_number.is_hash());
assert!(!block_id_number.is_latest());
assert!(!block_id_number.is_pending());
assert!(!block_id_number.is_safe());
assert!(!block_id_number.is_finalized());
assert!(!block_id_number.is_earliest());
let block_latest = BlockId::latest();
assert!(block_latest.is_latest());
assert!(!block_latest.is_number());
assert!(!block_latest.is_hash());
assert!(!block_latest.is_pending());
assert!(!block_latest.is_safe());
assert!(!block_latest.is_finalized());
assert!(!block_latest.is_earliest());
let block_pending = BlockId::pending();
assert!(block_pending.is_pending());
assert!(!block_pending.is_latest());
assert!(!block_pending.is_number());
assert!(!block_pending.is_hash());
assert!(!block_pending.is_safe());
assert!(!block_pending.is_finalized());
assert!(!block_pending.is_earliest());
let block_safe = BlockId::safe();
assert!(block_safe.is_safe());
assert!(!block_safe.is_latest());
assert!(!block_safe.is_number());
assert!(!block_safe.is_hash());
assert!(!block_safe.is_pending());
assert!(!block_safe.is_finalized());
assert!(!block_safe.is_earliest());
let block_finalized = BlockId::finalized();
assert!(block_finalized.is_finalized());
assert!(!block_finalized.is_latest());
assert!(!block_finalized.is_number());
assert!(!block_finalized.is_hash());
assert!(!block_finalized.is_pending());
assert!(!block_finalized.is_safe());
assert!(!block_finalized.is_earliest());
let block_earliest = BlockId::earliest();
assert!(block_earliest.is_earliest());
assert!(!block_earliest.is_latest());
assert!(!block_earliest.is_number());
assert!(!block_earliest.is_hash());
assert!(!block_earliest.is_pending());
assert!(!block_earliest.is_safe());
assert!(!block_earliest.is_finalized());
assert!(BlockId::default().is_latest());
assert!(!BlockId::default().is_number());
assert!(!BlockId::default().is_hash());
assert!(!BlockId::default().is_pending());
assert!(!BlockId::default().is_safe());
assert!(!BlockId::default().is_finalized());
assert!(!BlockId::default().is_earliest());
}
#[test]
fn test_u64_to_block_id() {
let num: u64 = 123;
let block_id: BlockId = num.into();
match block_id {
BlockId::Number(BlockNumberOrTag::Number(n)) => assert_eq!(n, 123),
_ => panic!("Expected BlockId::Number with 123"),
}
let num: U64 = U64::from(456);
let block_id: BlockId = num.into();
match block_id {
BlockId::Number(BlockNumberOrTag::Number(n)) => assert_eq!(n, 456),
_ => panic!("Expected BlockId::Number with 456"),
}
let num: u64 = 789;
let block_id: BlockId = HashOrNumber::Number(num).into();
match block_id {
BlockId::Number(BlockNumberOrTag::Number(n)) => assert_eq!(n, 789),
_ => panic!("Expected BlockId::Number with 789"),
}
}
#[test]
fn test_block_number_or_tag_to_block_id() {
let block_number_or_tag = BlockNumberOrTag::Pending;
let block_id: BlockId = block_number_or_tag.into();
match block_id {
BlockId::Number(BlockNumberOrTag::Pending) => {}
_ => panic!("Expected BlockId::Number with Pending"),
}
}
#[test]
fn test_hash_or_number_to_block_id_hash() {
let hash: B256 = B256::random();
let block_id: BlockId = HashOrNumber::Hash(hash).into();
match block_id {
BlockId::Hash(rpc_block_hash) => assert_eq!(rpc_block_hash.block_hash, hash),
_ => panic!("Expected BlockId::Hash"),
}
let hash: B256 = B256::random();
let block_id: BlockId = hash.into();
match block_id {
BlockId::Hash(rpc_block_hash) => assert_eq!(rpc_block_hash.block_hash, hash),
_ => panic!("Expected BlockId::Hash"),
}
let hash: B256 = B256::random();
let block_id: BlockId = (hash, Some(true)).into();
match block_id {
BlockId::Hash(rpc_block_hash) => {
assert_eq!(rpc_block_hash.block_hash, hash);
assert_eq!(rpc_block_hash.require_canonical, Some(true));
}
_ => panic!("Expected BlockId::Hash with canonical flag"),
}
}
#[test]
fn test_hash_or_number_as_number() {
let hash_or_number = HashOrNumber::Number(123);
assert_eq!(hash_or_number.as_number(), Some(123));
let hash = B256::random();
let hash_or_number = HashOrNumber::Hash(hash);
assert_eq!(hash_or_number.as_number(), None);
}
#[test]
fn test_hash_or_number_as_hash() {
let hash = B256::random();
let hash_or_number = HashOrNumber::Hash(hash);
assert_eq!(hash_or_number.as_hash(), Some(hash));
let hash_or_number = HashOrNumber::Number(456);
assert_eq!(hash_or_number.as_hash(), None);
}
#[test]
fn test_hash_or_number_conversions() {
let hash = B256::random();
let hash_or_number: HashOrNumber = hash.into();
assert_eq!(hash_or_number, HashOrNumber::Hash(hash));
let hash_ref: HashOrNumber = (&hash).into();
assert_eq!(hash_ref, HashOrNumber::Hash(hash));
let number: u64 = 123;
let hash_or_number: HashOrNumber = number.into();
assert_eq!(hash_or_number, HashOrNumber::Number(number));
let u64_value = U64::from(456);
let hash_or_number: HashOrNumber = u64_value.into();
assert_eq!(hash_or_number, HashOrNumber::Number(u64_value.to::<u64>()));
let rpc_block_hash = RpcBlockHash { block_hash: hash, require_canonical: Some(true) };
let hash_or_number: HashOrNumber = rpc_block_hash.into();
assert_eq!(hash_or_number, HashOrNumber::Hash(hash));
}
#[test]
fn test_hash_or_number_rlp_roundtrip_hash() {
let original_hash = B256::random();
let hash_or_number: HashOrNumber = HashOrNumber::Hash(original_hash);
let mut buf = Vec::new();
hash_or_number.encode(&mut buf);
let decoded: HashOrNumber = HashOrNumber::decode(&mut &buf[..]).expect("Decoding failed");
assert_eq!(decoded, hash_or_number);
}
#[test]
fn test_hash_or_number_rlp_roundtrip_u64() {
let original_number: u64 = 12345;
let hash_or_number: HashOrNumber = HashOrNumber::Number(original_number);
let mut buf = Vec::new();
hash_or_number.encode(&mut buf);
let decoded: HashOrNumber = HashOrNumber::decode(&mut &buf[..]).expect("Decoding failed");
assert_eq!(decoded, hash_or_number);
}
#[test]
fn test_numhash() {
let number: u64 = 42;
let hash = B256::random();
let num_hash = NumHash::new(number, hash);
assert_eq!(num_hash.number, number);
assert_eq!(num_hash.hash, hash);
assert_eq!(num_hash.into_components(), (number, hash));
}
#[test]
fn test_numhash_matches_block_or_num() {
let number: u64 = 42;
let hash = B256::random();
let num_hash = NumHash::new(number, hash);
let block_hash = HashOrNumber::Hash(hash);
assert!(num_hash.matches_block_or_num(&block_hash));
let block_number = HashOrNumber::Number(number);
assert!(num_hash.matches_block_or_num(&block_number));
let different_hash = B256::random();
let non_matching_hash = HashOrNumber::Hash(different_hash);
assert!(!num_hash.matches_block_or_num(&non_matching_hash));
let different_number: u64 = 43;
let non_matching_number = HashOrNumber::Number(different_number);
assert!(!num_hash.matches_block_or_num(&non_matching_number));
}
#[test]
fn test_numhash_conversions() {
let number: u64 = 42;
let hash = B256::random();
let num_hash_from_tuple: NumHash = (number, hash).into();
assert_eq!(num_hash_from_tuple.number, number);
assert_eq!(num_hash_from_tuple.hash, hash);
let number: u64 = 42;
let hash = B256::random();
let num_hash_from_reversed_tuple: NumHash = (hash, number).into();
assert_eq!(num_hash_from_reversed_tuple.number, number);
assert_eq!(num_hash_from_reversed_tuple.hash, hash);
}
#[test]
fn test_block_id_from_str() {
let hex_id = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
assert_eq!(
BlockId::from_str(hex_id).unwrap(),
BlockId::Hash(RpcBlockHash::from_hash(B256::from_str(hex_id).unwrap(), None))
);
assert_eq!(BlockId::from_str("latest").unwrap(), BlockNumberOrTag::Latest.into());
assert_eq!(BlockId::from_str("finalized").unwrap(), BlockNumberOrTag::Finalized.into());
assert_eq!(BlockId::from_str("safe").unwrap(), BlockNumberOrTag::Safe.into());
assert_eq!(BlockId::from_str("earliest").unwrap(), BlockNumberOrTag::Earliest.into());
assert_eq!(BlockId::from_str("pending").unwrap(), BlockNumberOrTag::Pending.into());
let numeric_string = "12345";
let parsed_numeric_string = BlockId::from_str(numeric_string);
assert!(parsed_numeric_string.is_ok());
assert_eq!(
BlockId::from_str("0x12345").unwrap(),
BlockId::Number(BlockNumberOrTag::Number(74565))
);
let invalid_string = "invalid_block_id";
let parsed_invalid_string = BlockId::from_str(invalid_string);
assert!(parsed_invalid_string.is_err());
}
}