extern crate alloc;
use miden_field_repr::FromFeltRepr;
use miden_stdlib_sys::{Felt, Word, felt};
pub fn padded_word_from_felt(value: Felt) -> Word {
Word::new([value, felt!(0), felt!(0), felt!(0)])
}
pub fn felt_from_padded_word(value: Word) -> Result<Felt, &'static str> {
if value[1] != felt!(0) || value[2] != felt!(0) || value[3] != felt!(0) {
return Err("expected zero padding in the trailing three felts");
}
Ok(value[0])
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromFeltRepr)]
pub struct AccountId {
pub prefix: Felt,
pub suffix: Felt,
}
impl AccountId {
pub fn new(prefix: Felt, suffix: Felt) -> Self {
Self { prefix, suffix }
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub(crate) struct RawAccountId {
pub suffix: Felt,
pub prefix: Felt,
}
impl RawAccountId {
pub(crate) fn into_account_id(self) -> AccountId {
AccountId::new(self.prefix, self.suffix)
}
}
impl From<AccountId> for Word {
#[inline]
fn from(value: AccountId) -> Self {
Word::from([felt!(0), felt!(0), value.suffix, value.prefix])
}
}
impl TryFrom<Word> for AccountId {
type Error = &'static str;
#[inline]
fn try_from(value: Word) -> Result<Self, Self::Error> {
if value[0] != felt!(0) || value[1] != felt!(0) {
return Err("expected zero padding in the upper two felts");
}
Ok(Self {
prefix: value[3],
suffix: value[2],
})
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct Asset {
pub key: Word,
pub value: Word,
}
impl Asset {
pub fn new(key: impl Into<Word>, value: impl Into<Word>) -> Self {
Self {
key: key.into(),
value: value.into(),
}
}
pub fn amount(&self) -> AssetAmount {
assert!(self.is_fungible(), "asset is not fungible");
let amount = self.value[0];
assert!(
amount <= AssetAmount::max_inner(),
"asset amount exceeds the maximum allowed amount"
);
AssetAmount { inner: amount }
}
#[inline]
pub fn is_fungible(&self) -> bool {
self.key[2].as_canonical_u64() & 1 == 1
}
}
impl From<Asset> for (Word, Word) {
fn from(val: Asset) -> Self {
(val.key, val.value)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum AssetAmountError {
AmountTooBig(u64),
}
impl core::fmt::Display for AssetAmountError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::AmountTooBig(amount) => {
write!(f, "asset amount {amount} exceeds the maximum {}", AssetAmount::MAX_U64)
}
}
}
}
impl core::error::Error for AssetAmountError {}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct AssetAmount {
#[doc(hidden)]
pub inner: Felt,
}
impl AssetAmount {
pub const MAX_U64: u64 = (1u64 << 63) - (1u64 << 31);
pub const ZERO: Self = Self { inner: Felt::ZERO };
#[inline]
pub fn max() -> Self {
Self {
inner: Self::max_inner(),
}
}
pub fn new(amount: u64) -> Result<Self, AssetAmountError> {
if amount > Self::MAX_U64 {
return Err(AssetAmountError::AmountTooBig(amount));
}
Ok(Self {
inner: Felt::new_unchecked(amount),
})
}
#[inline]
pub fn as_u64(&self) -> u64 {
self.inner.as_canonical_u64()
}
#[inline]
pub fn as_felt(&self) -> Felt {
self.inner
}
#[inline(always)]
fn max_inner() -> Felt {
Felt::new_unchecked(Self::MAX_U64)
}
#[inline]
fn amount_too_big(value: Felt) -> AssetAmountError {
AssetAmountError::AmountTooBig(value.as_canonical_u64())
}
}
const _: () = assert!(AssetAmount::MAX_U64 * 2 == Felt::ORDER - 1);
impl core::ops::Add for AssetAmount {
type Output = Self;
fn add(self, other: Self) -> Self {
let max = Self::max_inner();
assert!(self.inner <= max, "asset amount exceeds the maximum allowed amount");
let headroom = max - self.inner;
assert!(other.inner <= headroom, "asset amount addition overflow");
Self {
inner: self.inner + other.inner,
}
}
}
impl core::ops::Sub for AssetAmount {
type Output = Self;
fn sub(self, other: Self) -> Self {
let max = Self::max_inner();
assert!(self.inner <= max, "asset amount exceeds the maximum allowed amount");
assert!(other.inner <= self.inner, "asset amount subtraction underflow");
Self {
inner: self.inner - other.inner,
}
}
}
impl Default for AssetAmount {
fn default() -> Self {
Self::ZERO
}
}
impl From<u8> for AssetAmount {
fn from(value: u8) -> Self {
Self {
inner: Felt::from(value),
}
}
}
impl From<u16> for AssetAmount {
fn from(value: u16) -> Self {
Self {
inner: Felt::from(value),
}
}
}
impl From<u32> for AssetAmount {
fn from(value: u32) -> Self {
Self {
inner: Felt::from_u32(value),
}
}
}
impl TryFrom<u64> for AssetAmount {
type Error = AssetAmountError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<Felt> for AssetAmount {
type Error = AssetAmountError;
fn try_from(value: Felt) -> Result<Self, Self::Error> {
if value > Self::max_inner() {
return Err(Self::amount_too_big(value));
}
Ok(Self { inner: value })
}
}
impl From<AssetAmount> for u64 {
fn from(amount: AssetAmount) -> Self {
amount.as_u64()
}
}
impl From<AssetAmount> for Felt {
fn from(amount: AssetAmount) -> Self {
amount.inner
}
}
impl core::fmt::Display for AssetAmount {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.as_u64())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(transparent)]
pub struct Recipient {
pub inner: Word,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct NoteMetadata {
pub header: Word,
}
impl NoteMetadata {
pub fn new(header: Word) -> Self {
Self { header }
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub(crate) struct RawAttachmentLocation {
pub is_found: Felt,
pub index: Felt,
}
impl RawAttachmentLocation {
pub(crate) fn into_attachment_index(self) -> Option<u32> {
if self.is_found == Felt::ZERO {
return None;
}
Some(self.index.as_canonical_u64() as u32)
}
}
impl From<[Felt; 4]> for Recipient {
fn from(value: [Felt; 4]) -> Self {
Recipient {
inner: Word::from(value),
}
}
}
impl From<Word> for Recipient {
fn from(value: Word) -> Self {
Recipient { inner: value }
}
}
impl From<Recipient> for Word {
#[inline]
fn from(value: Recipient) -> Self {
value.inner
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(transparent)]
pub struct Tag {
pub inner: Felt,
}
impl From<Felt> for Tag {
fn from(value: Felt) -> Self {
Tag { inner: value }
}
}
impl From<Tag> for Word {
#[inline]
fn from(value: Tag) -> Self {
padded_word_from_felt(value.inner)
}
}
impl TryFrom<Word> for Tag {
type Error = &'static str;
#[inline]
fn try_from(value: Word) -> Result<Self, Self::Error> {
Ok(Tag {
inner: felt_from_padded_word(value)?,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(transparent)]
pub struct NoteIdx {
pub inner: Felt,
}
impl From<NoteIdx> for Word {
#[inline]
fn from(value: NoteIdx) -> Self {
padded_word_from_felt(value.inner)
}
}
impl TryFrom<Word> for NoteIdx {
type Error = &'static str;
#[inline]
fn try_from(value: Word) -> Result<Self, Self::Error> {
Ok(NoteIdx {
inner: felt_from_padded_word(value)?,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(transparent)]
pub struct NoteType {
pub inner: Felt,
}
impl From<Felt> for NoteType {
fn from(value: Felt) -> Self {
NoteType { inner: value }
}
}
impl From<NoteType> for Word {
#[inline]
fn from(value: NoteType) -> Self {
padded_word_from_felt(value.inner)
}
}
impl TryFrom<Word> for NoteType {
type Error = &'static str;
#[inline]
fn try_from(value: Word) -> Result<Self, Self::Error> {
Ok(NoteType {
inner: felt_from_padded_word(value)?,
})
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Nonce {
#[doc(hidden)]
pub inner: Felt,
}
impl Nonce {
#[inline]
pub fn as_u64(&self) -> u64 {
self.inner.as_canonical_u64()
}
#[inline]
pub fn as_felt(&self) -> Felt {
self.inner
}
}
impl From<Nonce> for Felt {
#[inline]
fn from(value: Nonce) -> Self {
value.inner
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct BlockNumber {
#[doc(hidden)]
pub inner: Felt,
}
impl BlockNumber {
#[inline]
pub fn as_u32(&self) -> u32 {
assert!(
self.inner <= Felt::from_u32(u32::MAX),
"block number exceeds the maximum block height"
);
self.inner.as_canonical_u64() as u32
}
#[inline]
pub fn as_felt(&self) -> Felt {
self.inner
}
}
impl From<u32> for BlockNumber {
fn from(value: u32) -> Self {
Self {
inner: Felt::from_u32(value),
}
}
}
impl TryFrom<Felt> for BlockNumber {
type Error = &'static str;
fn try_from(value: Felt) -> Result<Self, Self::Error> {
if value.as_canonical_u64() > u32::MAX as u64 {
return Err("block number exceeds the maximum block height");
}
Ok(Self { inner: value })
}
}
impl From<BlockNumber> for Felt {
#[inline]
fn from(value: BlockNumber) -> Self {
value.inner
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct StorageSlotId {
suffix: Felt,
prefix: Felt,
}
impl StorageSlotId {
pub fn new(suffix: Felt, prefix: Felt) -> Self {
Self { suffix, prefix }
}
pub fn from_prefix_suffix(prefix: Felt, suffix: Felt) -> Self {
Self { suffix, prefix }
}
pub fn to_prefix_suffix(&self) -> (Felt, Felt) {
(self.prefix, self.suffix)
}
pub fn to_suffix_prefix(&self) -> (Felt, Felt) {
(self.suffix, self.prefix)
}
pub fn suffix(&self) -> Felt {
self.suffix
}
pub fn prefix(&self) -> Felt {
self.prefix
}
}
#[cfg(test)]
mod tests {
use miden_stdlib_sys::{Felt, Word, felt};
use super::{
Asset, AssetAmount, AssetAmountError, BlockNumber, felt_from_padded_word,
padded_word_from_felt,
};
#[test]
fn padded_word_from_felt_zero_pads_trailing_limbs() {
assert_eq!(
padded_word_from_felt(felt!(7)),
Word::new([felt!(7), felt!(0), felt!(0), felt!(0)])
);
}
#[test]
fn felt_from_padded_word_rejects_non_zero_padding() {
let err =
felt_from_padded_word(Word::new([felt!(7), felt!(1), felt!(0), felt!(0)])).unwrap_err();
assert_eq!(err, "expected zero padding in the trailing three felts");
}
#[test]
fn felt_padding_helpers_roundtrip() {
let value = felt!(42);
assert_eq!(felt_from_padded_word(padded_word_from_felt(value)), Ok(value));
}
#[test]
fn asset_amount_valid_amounts() {
assert_eq!(AssetAmount::new(0).unwrap().as_u64(), 0);
assert_eq!(AssetAmount::new(1000).unwrap().as_u64(), 1000);
assert_eq!(AssetAmount::new(AssetAmount::MAX_U64).unwrap(), AssetAmount::max());
}
#[test]
fn asset_amount_exceeds_max() {
assert_eq!(
AssetAmount::new(AssetAmount::MAX_U64 + 1),
Err(AssetAmountError::AmountTooBig(AssetAmount::MAX_U64 + 1))
);
assert_eq!(AssetAmount::new(u64::MAX), Err(AssetAmountError::AmountTooBig(u64::MAX)));
}
#[test]
fn asset_amount_max_value() {
assert_eq!(AssetAmount::MAX_U64, 2u64.pow(63) - 2u64.pow(31));
assert_eq!(AssetAmount::max().as_u64(), AssetAmount::MAX_U64);
}
#[test]
fn asset_amount_from_small_types() {
assert_eq!(AssetAmount::from(42u8).as_u64(), 42);
assert_eq!(AssetAmount::from(1000u16).as_u64(), 1000);
assert_eq!(AssetAmount::from(u32::MAX).as_u64(), u32::MAX as u64);
}
#[test]
fn asset_amount_try_from() {
assert!(AssetAmount::try_from(AssetAmount::MAX_U64).is_ok());
assert!(AssetAmount::try_from(AssetAmount::MAX_U64 + 1).is_err());
assert!(AssetAmount::try_from(Felt::new(AssetAmount::MAX_U64).unwrap()).is_ok());
assert!(AssetAmount::try_from(Felt::new(AssetAmount::MAX_U64 + 1).unwrap()).is_err());
assert_eq!(
AssetAmount::try_from(Felt::new(Felt::ORDER - 1).unwrap()),
Err(AssetAmountError::AmountTooBig(Felt::ORDER - 1))
);
}
#[test]
fn asset_amount_add() {
let a = AssetAmount::new(100).unwrap();
let b = AssetAmount::new(200).unwrap();
assert_eq!((a + b).as_u64(), 300);
assert_eq!(AssetAmount::ZERO + AssetAmount::ZERO, AssetAmount::ZERO);
assert_eq!(AssetAmount::max() + AssetAmount::ZERO, AssetAmount::max());
}
#[test]
#[should_panic(expected = "asset amount addition overflow")]
fn asset_amount_add_panics_on_overflow() {
let _ = AssetAmount::max() + AssetAmount::new(1).unwrap();
}
#[test]
#[should_panic(expected = "asset amount exceeds the maximum allowed amount")]
fn asset_amount_add_panics_on_forged_lhs() {
let wrapping = AssetAmount {
inner: Felt::new(Felt::ORDER - 1).unwrap(),
};
let _ = wrapping + AssetAmount::new(1).unwrap();
}
#[test]
#[should_panic(expected = "asset amount addition overflow")]
fn asset_amount_add_panics_on_forged_rhs() {
let forged = AssetAmount {
inner: Felt::new(AssetAmount::MAX_U64 + 1).unwrap(),
};
let _ = AssetAmount::new(1).unwrap() + forged;
}
#[test]
fn asset_amount_sub() {
let a = AssetAmount::new(300).unwrap();
let b = AssetAmount::new(100).unwrap();
assert_eq!((a - b).as_u64(), 200);
assert_eq!(AssetAmount::ZERO - AssetAmount::ZERO, AssetAmount::ZERO);
assert_eq!(AssetAmount::max() - AssetAmount::max(), AssetAmount::ZERO);
}
#[test]
#[should_panic(expected = "asset amount subtraction underflow")]
fn asset_amount_sub_panics_on_underflow() {
let _ = AssetAmount::ZERO - AssetAmount::new(1).unwrap();
}
#[test]
#[should_panic(expected = "asset amount exceeds the maximum allowed amount")]
fn asset_amount_sub_panics_on_forged_minuend() {
let forged = AssetAmount {
inner: Felt::new(AssetAmount::MAX_U64 + 1).unwrap(),
};
let _ = forged - AssetAmount::new(1).unwrap();
}
#[test]
fn asset_amount_differential_vs_protocol() {
use miden_protocol::asset::AssetAmount as ProtocolAmount;
let values = [
0u64,
1,
2,
31,
u32::MAX as u64,
1 << 40,
AssetAmount::MAX_U64 / 2,
AssetAmount::MAX_U64 - 1,
AssetAmount::MAX_U64,
];
for &a in &values {
for &b in &values {
let ours = (AssetAmount::new(a).unwrap(), AssetAmount::new(b).unwrap());
let theirs = (ProtocolAmount::new(a).unwrap(), ProtocolAmount::new(b).unwrap());
if let Ok(sum) = theirs.0 + theirs.1 {
assert_eq!(
(ours.0 + ours.1).as_u64(),
sum.as_u64(),
"sum mismatch for {a} + {b}"
);
}
if let Ok(difference) = theirs.0 - theirs.1 {
assert_eq!(
(ours.0 - ours.1).as_u64(),
difference.as_u64(),
"difference mismatch for {a} - {b}"
);
}
}
}
}
#[test]
fn asset_amount_ordering() {
assert!(AssetAmount::new(1).unwrap() < AssetAmount::new(2).unwrap());
assert!(AssetAmount::max() > AssetAmount::ZERO);
assert_eq!(AssetAmount::default(), AssetAmount::ZERO);
}
#[test]
fn asset_amount_display() {
extern crate alloc;
use alloc::string::ToString;
assert_eq!(AssetAmount::new(12345).unwrap().to_string(), "12345");
}
#[test]
fn asset_amount_felt_roundtrip() {
let amount = AssetAmount::new(500).unwrap();
assert_eq!(amount.as_felt(), felt!(500));
assert_eq!(Felt::from(amount), felt!(500));
assert_eq!(u64::from(amount), 500);
}
fn fungible_asset(amount: Felt) -> Asset {
Asset::new(
Word::new([felt!(0), felt!(0), felt!(1), felt!(0)]),
Word::new([amount, felt!(0), felt!(0), felt!(0)]),
)
}
#[test]
fn asset_is_fungible() {
let non_fungible = Asset::new(
Word::new([felt!(0), felt!(0), felt!(2), felt!(0)]),
Word::new([felt!(42), felt!(0), felt!(0), felt!(0)]),
);
assert!(fungible_asset(felt!(42)).is_fungible());
assert!(!non_fungible.is_fungible());
}
#[test]
fn asset_amount_decodes_valid_fungible_assets() {
let asset = fungible_asset(felt!(42));
let callback_asset =
Asset::new(Word::new([felt!(0), felt!(0), felt!(5), felt!(0)]), asset.value);
assert_eq!(asset.amount(), AssetAmount::new(42).unwrap());
assert_eq!(callback_asset.amount(), AssetAmount::new(42).unwrap());
}
#[test]
#[should_panic(expected = "asset is not fungible")]
fn asset_amount_panics_on_non_fungible() {
let non_fungible = Asset::new(
Word::new([felt!(1), felt!(0), felt!(0), felt!(0)]),
Word::new([felt!(42), felt!(0), felt!(0), felt!(0)]),
);
let _ = non_fungible.amount();
}
#[test]
#[should_panic(expected = "asset amount exceeds the maximum allowed amount")]
fn asset_amount_panics_on_excessive_amount() {
let excessive_amount = fungible_asset(Felt::new(AssetAmount::MAX_U64 + 1).unwrap());
let _ = excessive_amount.amount();
}
#[test]
fn block_number_try_from_felt_bounds() {
let max = Felt::new(u32::MAX as u64).unwrap();
assert_eq!(BlockNumber::try_from(max).unwrap().as_u32(), u32::MAX);
assert!(BlockNumber::try_from(Felt::new(u32::MAX as u64 + 1).unwrap()).is_err());
}
#[test]
#[should_panic(expected = "block number exceeds the maximum block height")]
fn block_number_as_u32_panics_on_out_of_range_felt() {
let forged = BlockNumber {
inner: Felt::new(u32::MAX as u64 + 1).unwrap(),
};
let _ = forged.as_u32();
}
}