use miden_standards::interop::eth::AddressConversionError;
pub use miden_standards::interop::eth::EthEmbeddedAccountId;
use super::error::EncodingError;
pub trait EthEmbeddedAccountIdExt: Sized {
fn try_from_bytes32(bytes: [u8; 32]) -> Result<Self, EncodingError>;
}
impl EthEmbeddedAccountIdExt for EthEmbeddedAccountId {
fn try_from_bytes32(bytes: [u8; 32]) -> Result<Self, EncodingError> {
if bytes[..12] != [0u8; 12] {
return Err(EncodingError::AccountIdOutOfRange);
}
let embedded: [u8; 20] = bytes[12..].try_into().expect("32 minus 12 is 20 bytes");
Self::try_from(embedded).map_err(|source| match source {
AddressConversionError::NonZeroBytePrefix => EncodingError::AccountIdOutOfRange,
_ => EncodingError::NonCanonicalAccountId,
})
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use rstest::rstest;
use super::*;
use crate::vectors::{load, parse_hex32};
#[test]
fn tv_aid_1_roundtrip_lossless() {
let v = load();
for vec in v.families.aid.iter().filter(|v| v.expected_variant.is_none()) {
let b = parse_hex32(&vec.bytes32);
let embedded = EthEmbeddedAccountId::try_from_bytes32(b)
.unwrap_or_else(|e| panic!("vector {}: must decode, got {e}", vec.id));
assert_eq!(embedded.to_bytes32(), b, "vector {}: round-trip", vec.id);
}
}
#[rstest]
#[case::out_of_range("aid-rej-out-of-range")]
#[case::non_canonical("aid-rej-non-canonical")]
fn tv_aid_2_rejects(#[case] id: &str) {
let v = load();
let vec = v.families.aid.iter().find(|v| v.id == id).expect("vector present");
let b = parse_hex32(&vec.bytes32);
let result = EthEmbeddedAccountId::try_from_bytes32(b);
match vec.expected_variant.as_deref() {
Some("AccountIdOutOfRange") => {
assert_matches!(result, Err(EncodingError::AccountIdOutOfRange), "vector {id}")
},
Some("NonCanonicalAccountId") => {
assert_matches!(result, Err(EncodingError::NonCanonicalAccountId), "vector {id}")
},
other => panic!("vector {id}: unexpected expected_variant {other:?}"),
}
}
#[rstest]
#[case::b0(0)]
#[case::b1(1)]
#[case::b2(2)]
#[case::b3(3)]
#[case::b4(4)]
#[case::b5(5)]
#[case::b6(6)]
#[case::b7(7)]
#[case::b8(8)]
#[case::b9(9)]
#[case::b10(10)]
#[case::b11(11)]
#[case::b12(12)]
#[case::b13(13)]
#[case::b14(14)]
#[case::b15(15)]
fn nonzero_pad_byte_rejects_at_every_index(#[case] pad_index: usize) {
let v = load();
let vec = v.families.aid.iter().find(|v| v.id == "aid-rt-1").expect("vector present");
let mut b = parse_hex32(&vec.bytes32);
b[pad_index] = 0x01;
assert_matches!(
EthEmbeddedAccountId::try_from_bytes32(b),
Err(EncodingError::AccountIdOutOfRange),
"pad byte {pad_index}"
);
}
#[test]
fn account_id_out_of_range_message_names_16_byte_region() {
assert_eq!(
EncodingError::AccountIdOutOfRange.to_string(),
"bytes set outside the 16-byte account id region",
"the AccountIdOutOfRange message must match the shipped 16-byte-pad layout"
);
}
}