use alloc::format;
use alloc::string::{String, ToString};
use core::fmt;
use miden_core::FieldElement;
use miden_protocol::Felt;
use miden_protocol::account::AccountId;
use miden_protocol::utils::{HexParseError, bytes_to_hex_string, hex_to_bytes};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EthAddressFormat([u8; 20]);
impl EthAddressFormat {
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 fn from_account_id(account_id: AccountId) -> Self {
let felts: [Felt; 2] = account_id.into();
let mut out = [0u8; 20];
out[4..12].copy_from_slice(&felts[0].as_int().to_be_bytes());
out[12..20].copy_from_slice(&felts[1].as_int().to_be_bytes());
Self(out)
}
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) -> [Felt; 5] {
let mut result = [Felt::ZERO; 5];
for (felt, chunk) in result.iter_mut().zip(self.0.chunks(4).skip(1).rev()) {
let value = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
*felt = Felt::try_from(value as u64).expect("u32 value should always fit in Felt");
}
result
}
pub fn to_account_id(&self) -> Result<AccountId, AddressConversionError> {
let (prefix, suffix) = Self::bytes20_to_prefix_suffix(self.0)?;
let prefix_felt =
Felt::try_from(prefix).map_err(|_| AddressConversionError::FeltOutOfField)?;
let suffix_felt =
Felt::try_from(suffix).map_err(|_| AddressConversionError::FeltOutOfField)?;
AccountId::try_from([prefix_felt, suffix_felt])
.map_err(|_| AddressConversionError::InvalidAccountId)
}
fn bytes20_to_prefix_suffix(bytes: [u8; 20]) -> Result<(u64, u64), AddressConversionError> {
if bytes[0..4] != [0, 0, 0, 0] {
return Err(AddressConversionError::NonZeroBytePrefix);
}
let prefix = u64::from_be_bytes(bytes[4..12].try_into().unwrap());
let suffix = u64::from_be_bytes(bytes[12..20].try_into().unwrap());
Ok((prefix, suffix))
}
}
impl fmt::Display for EthAddressFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_hex())
}
}
impl From<[u8; 20]> for EthAddressFormat {
fn from(bytes: [u8; 20]) -> Self {
Self(bytes)
}
}
impl From<AccountId> for EthAddressFormat {
fn from(account_id: AccountId) -> Self {
EthAddressFormat::from_account_id(account_id)
}
}
impl From<EthAddressFormat> for [u8; 20] {
fn from(addr: EthAddressFormat) -> Self {
addr.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AddressConversionError {
NonZeroWordPadding,
NonZeroBytePrefix,
InvalidHexLength,
InvalidHexChar(char),
HexParseError,
FeltOutOfField,
InvalidAccountId,
}
impl fmt::Display for AddressConversionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AddressConversionError::NonZeroWordPadding => write!(f, "non-zero word padding"),
AddressConversionError::NonZeroBytePrefix => {
write!(f, "address has non-zero 4-byte prefix")
},
AddressConversionError::InvalidHexLength => {
write!(f, "invalid hex length (expected 40 hex chars)")
},
AddressConversionError::InvalidHexChar(c) => write!(f, "invalid hex character: {}", c),
AddressConversionError::HexParseError => write!(f, "hex parse error"),
AddressConversionError::FeltOutOfField => {
write!(f, "packed 64-bit word does not fit in the field")
},
AddressConversionError::InvalidAccountId => write!(f, "invalid AccountId"),
}
}
}
impl From<HexParseError> for AddressConversionError {
fn from(_err: HexParseError) -> Self {
AddressConversionError::HexParseError
}
}