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::{
de::{MapAccess, Visitor},
ser::SerializeStruct,
Deserialize, Deserializer, Serialize, Serializer,
};
#[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 Serialize for BlockNumberOrTag {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: 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> Deserialize<'de> for BlockNumberOrTag {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: 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> {
let block = 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") {
let number = u64::from_str_radix(hex_val, 16);
Self::Number(number?)
} else {
return Err(HexStringMissingPrefixError::default().into());
}
}
};
Ok(block)
}
}
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 {
Self::Number(BlockNumberOrTag::Latest)
}
}
impl From<u64> for BlockId {
fn from(num: u64) -> Self {
BlockNumberOrTag::Number(num).into()
}
}
impl From<U64> for BlockId {
fn from(value: U64) -> Self {
BlockNumberOrTag::Number(value.to()).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) => {
RpcBlockHash { block_hash: hash, require_canonical: None }.into()
}
HashOrNumber::Number(num) => Self::Number(BlockNumberOrTag::Number(num)),
}
}
}
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 Serialize for BlockId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: 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> Deserialize<'de> for BlockId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct BlockIdVisitor;
impl<'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(BlockId::Hash(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: 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(BlockId::Number(number))
} else if let Some(block_hash) = block_hash {
Ok(BlockId::Hash(RpcBlockHash { block_hash, require_canonical }))
} 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 if s.len() == 66 {
B256::from_str(s).map(Into::into).map_err(ParseBlockIdError::FromHexError)
} else {
U64::from_str(s).map(Into::into).map_err(ParseBlockIdError::ParseError)
};
}
match s {
"latest" => Ok(BlockNumberOrTag::Latest.into()),
"finalized" => Ok(BlockNumberOrTag::Finalized.into()),
"safe" => Ok(BlockNumberOrTag::Safe.into()),
"earliest" => Ok(BlockNumberOrTag::Earliest.into()),
"pending" => Ok(BlockNumberOrTag::Pending.into()),
_ => s
.parse::<u64>()
.map_err(ParseBlockIdError::ParseIntError)
.map(|n| Self::Number(n.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),
}
}
}
impl From<B256> for HashOrNumber {
fn from(value: B256) -> Self {
Self::Hash(value)
}
}
impl From<&B256> for HashOrNumber {
fn from(value: &B256) -> Self {
Self::Hash(*value)
}
}
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 {
let hash = B256::decode(buf)?;
Ok(Self::Hash(hash))
} else {
Ok(Self::Number(u64::decode(buf)?))
}
}
}
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 alloy_primitives::b256;
use super::*;
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");
}
}