use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use miden_protocol::Felt;
use miden_protocol::utils::{
HexParseError,
bytes_to_hex_string,
bytes_to_packed_u32_elements,
hex_to_bytes,
};
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EthAddress([u8; 20]);
impl EthAddress {
pub const fn new(bytes: [u8; 20]) -> Self {
Self(bytes)
}
pub fn from_hex(hex_str: &str) -> Result<Self, AddressConversionError> {
let hex_part = hex_str.strip_prefix("0x").unwrap_or(hex_str);
if hex_part.len() != 40 {
return Err(AddressConversionError::InvalidHexLength);
}
let prefixed_hex = if hex_str.starts_with("0x") {
hex_str.to_string()
} else {
format!("0x{}", hex_str)
};
let bytes: [u8; 20] = hex_to_bytes(&prefixed_hex)?;
Ok(Self(bytes))
}
pub const fn as_bytes(&self) -> &[u8; 20] {
&self.0
}
pub const fn into_bytes(self) -> [u8; 20] {
self.0
}
pub fn to_hex(&self) -> String {
bytes_to_hex_string(self.0)
}
pub fn to_elements(&self) -> Vec<Felt> {
bytes_to_packed_u32_elements(&self.0)
}
}
impl fmt::Display for EthAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_hex())
}
}
impl From<[u8; 20]> for EthAddress {
fn from(bytes: [u8; 20]) -> Self {
Self(bytes)
}
}
impl TryFrom<[u8; 32]> for EthAddress {
type Error = AddressConversionError;
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
if bytes[0..12] != [0; 12] {
return Err(AddressConversionError::NonZeroBytes32Padding);
}
let addr: [u8; 20] = bytes[12..32].try_into().expect("slice is exactly 20 bytes");
Ok(Self(addr))
}
}
impl From<EthAddress> for [u8; 20] {
fn from(addr: EthAddress) -> Self {
addr.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum AddressConversionError {
#[error("non-zero word padding")]
NonZeroWordPadding,
#[error("address has non-zero 4-byte prefix")]
NonZeroBytePrefix,
#[error("leading 12 bytes must be zero for a bytes32-embedded address")]
NonZeroBytes32Padding,
#[error("invalid hex length (expected 40 hex chars)")]
InvalidHexLength,
#[error("invalid hex character: {0}")]
InvalidHexChar(char),
#[error("hex parse error")]
HexParseError,
#[error("packed 8-byte value does not fit in the field")]
FeltOutOfField,
#[error("invalid AccountId")]
InvalidAccountId,
}
impl From<HexParseError> for AddressConversionError {
fn from(_err: HexParseError) -> Self {
AddressConversionError::HexParseError
}
}