use alloc::string::{String, ToString};
use core::fmt;
use core::hash::Hash;
use miden_core::Felt;
use miden_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
use miden_processor::DeserializationError;
use crate::account::account_id::v0::{self, validate_prefix};
use crate::account::{AccountIdVersion, AccountStorageMode, AccountType};
use crate::errors::AccountIdError;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct AccountIdPrefixV0 {
prefix: Felt,
}
impl Hash for AccountIdPrefixV0 {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.prefix.inner().hash(state);
}
}
impl AccountIdPrefixV0 {
const SERIALIZED_SIZE: usize = 8;
pub fn new_unchecked(prefix: Felt) -> Self {
if cfg!(debug_assertions) {
validate_prefix(prefix)
.expect("AccountIdPrefix::new_unchecked called with invalid prefix");
}
AccountIdPrefixV0 { prefix }
}
pub fn new(prefix: Felt) -> Result<Self, AccountIdError> {
validate_prefix(prefix)?;
Ok(AccountIdPrefixV0 { prefix })
}
pub const fn as_felt(&self) -> Felt {
self.prefix
}
pub const fn as_u64(&self) -> u64 {
self.prefix.as_int()
}
pub const fn account_type(&self) -> AccountType {
v0::extract_type(self.prefix.as_int())
}
pub fn is_faucet(&self) -> bool {
self.account_type().is_faucet()
}
pub fn is_regular_account(&self) -> bool {
self.account_type().is_regular_account()
}
pub fn storage_mode(&self) -> AccountStorageMode {
v0::extract_storage_mode(self.prefix.as_int())
.expect("account ID prefix should have been constructed with a valid storage mode")
}
pub fn is_public(&self) -> bool {
self.storage_mode().is_public()
}
pub fn version(&self) -> AccountIdVersion {
v0::extract_version(self.prefix.as_int())
.expect("account ID prefix should have been constructed with a valid version")
}
pub fn to_hex(self) -> String {
format!("0x{:016x}", self.prefix.as_int())
}
}
impl From<AccountIdPrefixV0> for Felt {
fn from(id: AccountIdPrefixV0) -> Self {
id.prefix
}
}
impl From<AccountIdPrefixV0> for [u8; 8] {
fn from(id: AccountIdPrefixV0) -> Self {
let mut result = [0_u8; 8];
result[..8].copy_from_slice(&id.prefix.as_int().to_be_bytes());
result
}
}
impl From<AccountIdPrefixV0> for u64 {
fn from(id: AccountIdPrefixV0) -> Self {
id.prefix.as_int()
}
}
impl TryFrom<[u8; 8]> for AccountIdPrefixV0 {
type Error = AccountIdError;
fn try_from(mut value: [u8; 8]) -> Result<Self, Self::Error> {
value.reverse();
Felt::try_from(value.as_slice())
.map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)
.and_then(Self::new)
}
}
impl TryFrom<u64> for AccountIdPrefixV0 {
type Error = AccountIdError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
let element = Felt::try_from(value.to_le_bytes().as_slice())
.map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?;
Self::new(element)
}
}
impl TryFrom<Felt> for AccountIdPrefixV0 {
type Error = AccountIdError;
fn try_from(element: Felt) -> Result<Self, Self::Error> {
Self::new(element)
}
}
impl PartialOrd for AccountIdPrefixV0 {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AccountIdPrefixV0 {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.prefix.as_int().cmp(&other.prefix.as_int())
}
}
impl fmt::Display for AccountIdPrefixV0 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_hex())
}
}
impl Serializable for AccountIdPrefixV0 {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
let bytes: [u8; 8] = (*self).into();
bytes.write_into(target);
}
fn get_size_hint(&self) -> usize {
Self::SERIALIZED_SIZE
}
}
impl Deserializable for AccountIdPrefixV0 {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
<[u8; 8]>::read_from(source)?
.try_into()
.map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::account::{AccountId, AccountIdPrefix};
use crate::testing::account_id::{
ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PRIVATE_SENDER,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
};
#[test]
fn test_account_id_prefix_conversion_roundtrip() {
for (idx, account_id) in [
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PRIVATE_SENDER,
]
.into_iter()
.enumerate()
{
let full_id = AccountId::try_from(account_id).unwrap();
let prefix = full_id.prefix();
assert_eq!(
prefix,
AccountIdPrefix::read_from_bytes(&prefix.to_bytes()).unwrap(),
"failed in {idx}"
);
}
}
}