use miden_client::Felt;
use miden_client::account::AccountId;
use miden_client::vm::typed::{MIDEN_CORE_TYPES, TypedError, WitScalarCodec};
use crate::codecs::{ACCOUNT_ID_WIT_NAME, parse_account_id_token};
pub struct AccountIdCodec;
impl WitScalarCodec for AccountIdCodec {
fn wit_name(&self) -> &str {
ACCOUNT_ID_WIT_NAME
}
fn wit_interface(&self) -> Option<&str> {
Some(MIDEN_CORE_TYPES)
}
fn encode(&self, token: &str) -> Result<Vec<Felt>, TypedError> {
let id = parse_account_id_token(token)?;
Ok(vec![id.prefix().into(), id.suffix()])
}
fn decode(&self, felts: &[Felt]) -> Result<String, TypedError> {
let [prefix, suffix] = felts else {
return Err(TypedError::MalformedResult {
ty: ACCOUNT_ID_WIT_NAME.to_string(),
reason: "an account id occupies exactly two felts",
});
};
let id = AccountId::try_from_elements(*suffix, *prefix).map_err(|_| {
TypedError::MalformedResult {
ty: ACCOUNT_ID_WIT_NAME.to_string(),
reason: "the felts are not a valid account id",
}
})?;
Ok(format!("account-id({})", id.to_hex()))
}
}
#[cfg(test)]
mod tests {
use miden_client::address::{Address, NetworkId};
use super::*;
const HEX_ID: &str = "0xaa0000000000bb110000cc000000dd";
#[test]
fn account_id_one_hex_token_roundtrips() {
let codec = AccountIdCodec;
let hex = HEX_ID;
let id = AccountId::from_hex(hex).unwrap();
let expected = [Felt::from(id.prefix()), id.suffix()];
assert_eq!(codec.encode(hex).unwrap(), expected);
assert_eq!(codec.decode(&expected).unwrap(), format!("account-id({hex})"));
}
#[test]
fn felts_that_are_not_an_account_id_are_rejected() {
let err = AccountIdCodec.decode(&[Felt::from(1u32), Felt::from(2u32)]).unwrap_err();
assert!(matches!(err, TypedError::MalformedResult { .. }));
}
#[test]
fn a_bech32_token_encodes_to_the_same_felts_as_its_hex_spelling() {
let id = AccountId::from_hex(HEX_ID).unwrap();
let bech32 = Address::new(id).encode(NetworkId::Testnet);
assert_eq!(
AccountIdCodec.encode(&bech32).unwrap(),
AccountIdCodec.encode(&id.to_hex()).unwrap()
);
}
#[test]
fn an_invalid_account_id_token_is_rejected() {
let valid_bech32 =
Address::new(AccountId::from_hex(HEX_ID).unwrap()).encode(NetworkId::Testnet);
let tokens = [
"not-hex",
"0xnothex",
&valid_bech32[..valid_bech32.len() - 1],
];
for token in tokens {
let err = AccountIdCodec.encode(token).unwrap_err();
assert!(matches!(err, TypedError::InvalidScalar { .. }), "token '{token}' accepted");
}
}
}