Skip to main content

miden_base_sys/bindings/
types.rs

1extern crate alloc;
2
3use miden_field_repr::{FromFeltRepr, ToFeltRepr};
4use miden_stdlib_sys::{Felt, Word, felt};
5
6/// Packs a scalar felt into the leading limb of a protocol word.
7pub fn padded_word_from_felt(value: Felt) -> Word {
8    Word::new([value, felt!(0), felt!(0), felt!(0)])
9}
10
11/// Extracts a scalar felt from a protocol word with zero-padded trailing limbs.
12pub fn felt_from_padded_word(value: Word) -> Result<Felt, &'static str> {
13    if value[1] != felt!(0) || value[2] != felt!(0) || value[3] != felt!(0) {
14        return Err("expected zero padding in the trailing three felts");
15    }
16
17    Ok(value[0])
18}
19
20/// Unique identifier for a Miden account, composed of two field elements.
21#[derive(Copy, Clone, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
22pub struct AccountId {
23    pub prefix: Felt,
24    pub suffix: Felt,
25}
26
27impl AccountId {
28    /// Creates a new AccountId from prefix and suffix Felt values.
29    pub fn new(prefix: Felt, suffix: Felt) -> Self {
30        Self { prefix, suffix }
31    }
32}
33
34/// Raw protocol return layout for account identifiers.
35/// The protocol MASM procedures are returning [suffix, prefix]
36#[derive(Copy, Clone)]
37#[repr(C)]
38pub(crate) struct RawAccountId {
39    pub suffix: Felt,
40    pub prefix: Felt,
41}
42
43impl RawAccountId {
44    /// Converts the protocol return layout into the Rust [`AccountId`] layout.
45    pub(crate) fn into_account_id(self) -> AccountId {
46        AccountId::new(self.prefix, self.suffix)
47    }
48}
49
50impl From<AccountId> for Word {
51    #[inline]
52    fn from(value: AccountId) -> Self {
53        Word::from([felt!(0), felt!(0), value.suffix, value.prefix])
54    }
55}
56
57impl TryFrom<Word> for AccountId {
58    type Error = &'static str;
59
60    #[inline]
61    fn try_from(value: Word) -> Result<Self, Self::Error> {
62        if value[0] != felt!(0) || value[1] != felt!(0) {
63            return Err("expected zero padding in the upper two felts");
64        }
65
66        Ok(Self {
67            prefix: value[3],
68            suffix: value[2],
69        })
70    }
71}
72
73/// A fungible or non-fungible asset encoded as an asset id and a value word.
74///
75/// The `id` identifies the asset in a vault (issuing faucet, asset class, composition rule);
76/// `value` is the asset contents.
77#[derive(Copy, Clone, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
78#[repr(C)]
79pub struct Asset {
80    /// The asset's id, which identifies it in a vault.
81    pub id: AssetId,
82    /// The asset's contents.
83    pub value: Word,
84}
85
86impl Asset {
87    /// Creates a new [`Asset`] from its id and value word.
88    pub fn new(id: impl Into<AssetId>, value: impl Into<Word>) -> Self {
89        Self {
90            id: id.into(),
91            value: value.into(),
92        }
93    }
94
95    /// Returns this asset's id; the same value as the `id` field.
96    #[inline]
97    pub fn id(&self) -> AssetId {
98        self.id
99    }
100
101    /// Returns this asset's fungible amount.
102    ///
103    /// The composition rule is read from the asset id with [`AssetId::composition`], which
104    /// executes the protocol library's `asset::id_into_composition` procedure.
105    ///
106    /// # Panics
107    ///
108    /// Panics if the composition bits of the asset id hold an unrecognized value, if the asset is
109    /// not fungible, or if its amount exceeds [`AssetAmount::MAX_U64`].
110    pub fn amount(&self) -> AssetAmount {
111        assert!(self.is_fungible(), "asset is not fungible");
112        let amount = self.value[0];
113        assert!(
114            amount <= AssetAmount::max_inner(),
115            "asset amount exceeds the maximum allowed amount"
116        );
117        AssetAmount { inner: amount }
118    }
119
120    /// Returns `true` if this asset is fungible.
121    ///
122    /// The composition rule is read from the asset id with [`AssetId::composition`], which
123    /// executes the protocol library's `asset::id_into_composition` procedure.
124    ///
125    /// # Panics
126    ///
127    /// Panics if the composition bits of the asset id hold an unrecognized value.
128    #[inline]
129    pub fn is_fungible(&self) -> bool {
130        self.id.composition() == AssetComposition::Fungible
131    }
132}
133
134impl From<Asset> for (Word, Word) {
135    fn from(val: Asset) -> Self {
136        (val.id.inner, val.value)
137    }
138}
139
140/// The identifier of an asset, the word that identifies it in an account vault.
141///
142/// An asset id encodes the issuing faucet, the asset class and the composition rule; read them with
143/// [`AssetId::faucet_id`], [`AssetId::asset_class`] and [`AssetId::composition`] rather than
144/// decoding the limbs by hand, since the encoding is protocol-versioned.
145#[derive(Copy, Clone, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
146#[repr(transparent)]
147pub struct AssetId {
148    /// The asset id word, as the transaction kernel encodes it.
149    pub inner: Word,
150}
151
152impl From<Word> for AssetId {
153    #[inline]
154    fn from(value: Word) -> Self {
155        Self { inner: value }
156    }
157}
158
159impl From<[Felt; 4]> for AssetId {
160    #[inline]
161    fn from(value: [Felt; 4]) -> Self {
162        Self {
163            inner: Word::from(value),
164        }
165    }
166}
167
168impl From<AssetId> for Word {
169    #[inline]
170    fn from(value: AssetId) -> Self {
171        value.inner
172    }
173}
174
175/// The class of an asset, composed of two field elements.
176///
177/// The asset class distinguishes different assets issued by the same faucet; it is empty for
178/// fungible assets. Use [`AssetId::faucet_id`] to identify the issuer.
179#[derive(Copy, Clone, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
180pub struct AssetClass {
181    /// The prefix of the class, the second element of the kernel's `[suffix, prefix]` output.
182    pub prefix: Felt,
183    /// The suffix of the class, the first element of the kernel's `[suffix, prefix]` output.
184    pub suffix: Felt,
185}
186
187impl AssetClass {
188    /// Creates a new [`AssetClass`] from prefix and suffix Felt values.
189    pub fn new(prefix: Felt, suffix: Felt) -> Self {
190        Self { prefix, suffix }
191    }
192}
193
194/// Raw protocol return layout for asset classes.
195/// The protocol MASM procedures are returning [suffix, prefix]
196#[derive(Copy, Clone)]
197#[repr(C)]
198pub(crate) struct RawAssetClass {
199    /// The suffix of the class, the first element of the kernel's output.
200    pub suffix: Felt,
201    /// The prefix of the class, the second element of the kernel's output.
202    pub prefix: Felt,
203}
204
205impl RawAssetClass {
206    /// Converts the protocol return layout into the Rust [`AssetClass`] layout.
207    pub(crate) fn into_asset_class(self) -> AssetClass {
208        AssetClass::new(self.prefix, self.suffix)
209    }
210}
211
212/// How the value of an asset combines when the same asset id is added to a vault twice.
213#[derive(Copy, Clone, Debug, PartialEq, Eq)]
214pub enum AssetComposition {
215    /// The asset has no composition rule and cannot be held more than once.
216    None,
217    /// The asset's amounts add together, as for a fungible asset.
218    Fungible,
219    /// The asset composes under a faucet-defined rule. Not yet supported by the protocol.
220    Custom,
221}
222
223impl TryFrom<Felt> for AssetComposition {
224    type Error = &'static str;
225
226    #[inline]
227    fn try_from(value: Felt) -> Result<Self, Self::Error> {
228        match value.as_canonical_u64() {
229            0 => Ok(Self::None),
230            1 => Ok(Self::Fungible),
231            2 => Ok(Self::Custom),
232            _ => Err("unrecognized asset composition"),
233        }
234    }
235}
236
237/// An error produced while constructing an [`AssetAmount`] from an out-of-range value.
238#[derive(Copy, Clone, Debug, PartialEq, Eq)]
239pub enum AssetAmountError {
240    /// The amount exceeds [`AssetAmount::MAX_U64`].
241    AmountTooBig(u64),
242}
243
244impl core::fmt::Display for AssetAmountError {
245    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
246        match self {
247            Self::AmountTooBig(amount) => {
248                write!(f, "asset amount {amount} exceeds the maximum {}", AssetAmount::MAX_U64)
249            }
250        }
251    }
252}
253
254impl core::error::Error for AssetAmountError {}
255
256/// A validated fungible asset amount.
257///
258/// Values created through this type's constructors, conversions, and arithmetic operations wrap
259/// a [`Felt`] whose canonical value is at most [`AssetAmount::MAX_U64`]. The API mirrors
260/// `miden_protocol::asset::AssetAmount` so that on-chain and off-chain code handle amounts the
261/// same way, while the felt representation avoids integer/felt conversions around the
262/// transaction kernel procedures.
263///
264/// Unlike a raw [`Felt`], an amount only offers integer semantics: addition and subtraction
265/// panic on overflow and underflow instead of wrapping, and comparison follows the canonical
266/// integer value. Finite field arithmetic (wrapping at the field modulus, division via the
267/// multiplicative inverse) is intentionally unavailable; convert with [`AssetAmount::as_u64`]
268/// when full integer functionality is needed.
269#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
270#[repr(transparent)]
271pub struct AssetAmount {
272    /// The raw field representation.
273    ///
274    /// Assigning this field directly bypasses amount validation; the checked arithmetic rejects
275    /// out-of-range operands. It is public only because component-model bindings construct WIT
276    /// records by field — use the checked constructors and accessors instead.
277    #[doc(hidden)]
278    pub inner: Felt,
279}
280
281impl AssetAmount {
282    /// The maximum value an asset amount can represent, equal to `2^63 - 2^31`.
283    ///
284    /// Matches `miden_protocol::asset::AssetAmount::MAX`, which is chosen so that an amount fits
285    /// in a field element as both a positive and a negative value.
286    // Felt constants on the Miden target are limited to 32-bit values, so the maximum amount
287    // cannot be an associated `AssetAmount` constant; see `Self::max`.
288    pub const MAX_U64: u64 = (1u64 << 63) - (1u64 << 31);
289    /// The zero amount.
290    pub const ZERO: Self = Self { inner: Felt::ZERO };
291
292    /// Returns the maximum representable asset amount, equal to [`Self::MAX_U64`].
293    #[inline]
294    pub fn max() -> Self {
295        Self {
296            inner: Self::max_inner(),
297        }
298    }
299
300    /// Returns a new asset amount if `amount` does not exceed [`Self::MAX_U64`].
301    ///
302    /// # Errors
303    ///
304    /// Returns an error if `amount` is greater than [`Self::MAX_U64`].
305    pub fn new(amount: u64) -> Result<Self, AssetAmountError> {
306        if amount > Self::MAX_U64 {
307            return Err(AssetAmountError::AmountTooBig(amount));
308        }
309        // The bound check above also guarantees the value is below the field modulus.
310        Ok(Self {
311            inner: Felt::new_unchecked(amount),
312        })
313    }
314
315    /// Returns the amount as a `u64` value.
316    #[inline]
317    pub fn as_u64(&self) -> u64 {
318        self.inner.as_canonical_u64()
319    }
320
321    /// Returns the amount as a raw [`Felt`] for advanced use.
322    #[inline]
323    pub fn as_felt(&self) -> Felt {
324        self.inner
325    }
326
327    /// Returns the maximum amount as a raw felt.
328    #[inline(always)]
329    fn max_inner() -> Felt {
330        // MAX_U64 is below the field modulus, so no reduction occurs.
331        Felt::new_unchecked(Self::MAX_U64)
332    }
333
334    /// Builds the out-of-range error for the provided felt.
335    #[inline]
336    fn amount_too_big(value: Felt) -> AssetAmountError {
337        // The felt-to-integer conversion only runs on error paths.
338        AssetAmountError::AmountTooBig(value.as_canonical_u64())
339    }
340}
341
342// Two maximal amounts must sum to exactly the field modulus minus one; the checked arithmetic
343// below relies on this to rule out field wrap-around for validated operands.
344const _: () = assert!(AssetAmount::MAX_U64 * 2 == Felt::ORDER - 1);
345
346impl core::ops::Add for AssetAmount {
347    type Output = Self;
348
349    /// Adds two asset amounts, staying in the field domain.
350    ///
351    /// # Panics
352    ///
353    /// Panics if either operand or the sum exceeds [`AssetAmount::MAX_U64`].
354    fn add(self, other: Self) -> Self {
355        let max = Self::max_inner();
356        // Reject an out-of-range operand (possible via direct `inner` assignment) before
357        // relying on its value.
358        assert!(self.inner <= max, "asset amount exceeds the maximum allowed amount");
359        // `self` is in range, so this felt subtraction is exact and the headroom is at most
360        // MAX_U64. One comparison then proves both that `other` is in range
361        // (other <= headroom <= MAX_U64) and that the sum stays in range
362        // (self + other <= MAX_U64), so the felt addition below cannot wrap around.
363        let headroom = max - self.inner;
364        assert!(other.inner <= headroom, "asset amount addition overflow");
365        Self {
366            inner: self.inner + other.inner,
367        }
368    }
369}
370
371impl core::ops::Sub for AssetAmount {
372    type Output = Self;
373
374    /// Subtracts `other` from `self`, staying in the field domain.
375    ///
376    /// # Panics
377    ///
378    /// Panics if either operand exceeds [`AssetAmount::MAX_U64`] or if `other` is greater than
379    /// `self`.
380    fn sub(self, other: Self) -> Self {
381        let max = Self::max_inner();
382        // An out-of-range minuend (possible via direct `inner` assignment) could otherwise
383        // produce an out-of-range result.
384        assert!(self.inner <= max, "asset amount exceeds the maximum allowed amount");
385        // When this check passes, other <= self <= MAX_U64, so `other` is in range, the felt
386        // subtraction cannot wrap around, and the result needs no validation.
387        assert!(other.inner <= self.inner, "asset amount subtraction underflow");
388        Self {
389            inner: self.inner - other.inner,
390        }
391    }
392}
393
394impl Default for AssetAmount {
395    fn default() -> Self {
396        Self::ZERO
397    }
398}
399
400impl From<u8> for AssetAmount {
401    fn from(value: u8) -> Self {
402        Self {
403            inner: Felt::from(value),
404        }
405    }
406}
407
408impl From<u16> for AssetAmount {
409    fn from(value: u16) -> Self {
410        Self {
411            inner: Felt::from(value),
412        }
413    }
414}
415
416impl From<u32> for AssetAmount {
417    fn from(value: u32) -> Self {
418        // Any u32 value is below the maximum amount.
419        Self {
420            inner: Felt::from_u32(value),
421        }
422    }
423}
424
425impl TryFrom<u64> for AssetAmount {
426    type Error = AssetAmountError;
427
428    fn try_from(value: u64) -> Result<Self, Self::Error> {
429        Self::new(value)
430    }
431}
432
433impl TryFrom<Felt> for AssetAmount {
434    type Error = AssetAmountError;
435
436    fn try_from(value: Felt) -> Result<Self, Self::Error> {
437        if value > Self::max_inner() {
438            return Err(Self::amount_too_big(value));
439        }
440        Ok(Self { inner: value })
441    }
442}
443
444impl From<AssetAmount> for u64 {
445    fn from(amount: AssetAmount) -> Self {
446        amount.as_u64()
447    }
448}
449
450impl From<AssetAmount> for Felt {
451    fn from(amount: AssetAmount) -> Self {
452        amount.inner
453    }
454}
455
456impl core::fmt::Display for AssetAmount {
457    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
458        write!(f, "{}", self.as_u64())
459    }
460}
461
462/// A note recipient digest.
463#[derive(Clone, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
464#[repr(transparent)]
465pub struct Recipient {
466    pub inner: Word,
467}
468
469/// The unique identifier of a note: `hash(NOTE_DETAILS_COMMITMENT || NOTE_METADATA_COMMITMENT)`,
470/// where the details commitment covers the note's recipient and assets commitment.
471#[derive(Copy, Clone, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
472#[repr(transparent)]
473pub struct NoteId {
474    /// The note id digest.
475    pub inner: Word,
476}
477
478impl From<Word> for NoteId {
479    #[inline]
480    fn from(value: Word) -> Self {
481        Self { inner: value }
482    }
483}
484
485impl From<NoteId> for Word {
486    #[inline]
487    fn from(value: NoteId) -> Self {
488        value.inner
489    }
490}
491
492/// Raw protocol return layout for procedures that leave a commitment word followed by a count.
493///
494/// Used by the note asset and storage summaries (`get_initial_assets_info`, `get_storage_info`,
495/// `get_assets_info`), whose stack outputs are `[COMMITMENT, count]`.
496#[derive(Copy, Clone)]
497#[repr(C)]
498pub(crate) struct RawCommitmentWithCount {
499    /// The commitment word.
500    pub commitment: Word,
501    /// The count that follows the commitment on the stack.
502    pub count: Felt,
503}
504
505impl RawCommitmentWithCount {
506    /// Returns the count as an integer.
507    pub(crate) fn num_items(&self) -> u32 {
508        // The transaction kernel guarantees asset and storage item counts fit in a u32.
509        self.count.as_canonical_u64() as u32
510    }
511}
512
513/// The note metadata returned by `*_note::get_metadata` procedures.
514///
515/// In the Miden protocol, metadata retrieval returns a single metadata header word. Note
516/// attachments are retrieved separately via the `*_note::get_attachments_commitment`,
517/// `find_attachment`, and `write_attachment_*` procedures.
518#[derive(Copy, Clone, Debug, PartialEq, Eq)]
519#[repr(C)]
520pub struct NoteMetadata {
521    /// The metadata header of the note.
522    pub header: Word,
523}
524
525impl NoteMetadata {
526    /// Creates a new [`NoteMetadata`] from the metadata header word.
527    pub fn new(header: Word) -> Self {
528        Self { header }
529    }
530}
531
532/// Raw protocol return layout for lookups whose stack outputs are `[is_found, index]`.
533///
534/// Used by the attachment lookups (`find_attachment`) and the input-note lookup (`find_note`).
535#[derive(Copy, Clone)]
536#[repr(C)]
537pub(crate) struct RawFoundIndex {
538    /// Non-zero when the lookup found a match.
539    pub is_found: Felt,
540    /// The index of the match, valid only when `is_found` is non-zero.
541    pub index: Felt,
542}
543
544impl RawFoundIndex {
545    /// Returns the index of the match, if there was one.
546    fn index(self) -> Option<Felt> {
547        (self.is_found != Felt::ZERO).then_some(self.index)
548    }
549
550    /// Converts the protocol return layout into the found attachment index, if any.
551    pub(crate) fn into_attachment_index(self) -> Option<u32> {
552        // The transaction kernel guarantees attachment indexes fit in a u32.
553        self.index().map(|index| index.as_canonical_u64() as u32)
554    }
555
556    /// Converts the protocol return layout into the found input-note index, if any.
557    pub(crate) fn into_note_index(self) -> Option<NoteIdx> {
558        self.index().map(|index| NoteIdx { inner: index })
559    }
560}
561
562impl From<[Felt; 4]> for Recipient {
563    fn from(value: [Felt; 4]) -> Self {
564        Recipient {
565            inner: Word::from(value),
566        }
567    }
568}
569
570impl From<Word> for Recipient {
571    fn from(value: Word) -> Self {
572        Recipient { inner: value }
573    }
574}
575
576impl From<Recipient> for Word {
577    #[inline]
578    fn from(value: Recipient) -> Self {
579        value.inner
580    }
581}
582
583#[derive(Clone, Copy, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
584#[repr(transparent)]
585pub struct Tag {
586    pub inner: Felt,
587}
588
589impl From<Felt> for Tag {
590    fn from(value: Felt) -> Self {
591        Tag { inner: value }
592    }
593}
594
595impl From<Tag> for Word {
596    #[inline]
597    fn from(value: Tag) -> Self {
598        padded_word_from_felt(value.inner)
599    }
600}
601
602impl TryFrom<Word> for Tag {
603    type Error = &'static str;
604
605    #[inline]
606    fn try_from(value: Word) -> Result<Self, Self::Error> {
607        Ok(Tag {
608            inner: felt_from_padded_word(value)?,
609        })
610    }
611}
612
613#[derive(Clone, Copy, Debug, PartialEq, Eq)]
614#[repr(transparent)]
615pub struct NoteIdx {
616    pub inner: Felt,
617}
618
619impl From<NoteIdx> for Word {
620    #[inline]
621    fn from(value: NoteIdx) -> Self {
622        padded_word_from_felt(value.inner)
623    }
624}
625
626impl TryFrom<Word> for NoteIdx {
627    type Error = &'static str;
628
629    #[inline]
630    fn try_from(value: Word) -> Result<Self, Self::Error> {
631        Ok(NoteIdx {
632            inner: felt_from_padded_word(value)?,
633        })
634    }
635}
636
637#[derive(Clone, Copy, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
638#[repr(transparent)]
639pub struct NoteType {
640    pub inner: Felt,
641}
642
643impl From<Felt> for NoteType {
644    fn from(value: Felt) -> Self {
645        NoteType { inner: value }
646    }
647}
648
649impl From<NoteType> for Word {
650    #[inline]
651    fn from(value: NoteType) -> Self {
652        padded_word_from_felt(value.inner)
653    }
654}
655
656impl TryFrom<Word> for NoteType {
657    type Error = &'static str;
658
659    #[inline]
660    fn try_from(value: Word) -> Result<Self, Self::Error> {
661        Ok(NoteType {
662            inner: felt_from_padded_word(value)?,
663        })
664    }
665}
666
667/// An account nonce: a counter the transaction kernel increments once per state-changing
668/// transaction.
669///
670/// Nonces compare as integers; they are produced by the account bindings and carry no
671/// arithmetic of their own.
672#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
673#[repr(transparent)]
674pub struct Nonce {
675    /// The raw field representation. Public only because component-model bindings construct
676    /// WIT records by field.
677    #[doc(hidden)]
678    pub inner: Felt,
679}
680
681impl Nonce {
682    /// Returns the nonce as a `u64` value.
683    #[inline]
684    pub fn as_u64(&self) -> u64 {
685        self.inner.as_canonical_u64()
686    }
687
688    /// Returns the nonce as a raw [`Felt`] for advanced use.
689    #[inline]
690    pub fn as_felt(&self) -> Felt {
691        self.inner
692    }
693}
694
695impl From<Nonce> for Felt {
696    #[inline]
697    fn from(value: Nonce) -> Self {
698        value.inner
699    }
700}
701
702/// A block height in the chain.
703///
704/// Block numbers compare as integers and are bounded to `u32` by the protocol. Kernel-returned
705/// heights are trusted; raw felts (e.g. read from note storage) convert via the validated
706/// [`TryFrom<Felt>`] implementation.
707#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
708#[repr(transparent)]
709pub struct BlockNumber {
710    /// The raw field representation. Public only because component-model bindings construct
711    /// WIT records by field.
712    #[doc(hidden)]
713    pub inner: Felt,
714}
715
716impl BlockNumber {
717    /// Returns the block number as a `u32` value.
718    ///
719    /// # Panics
720    ///
721    /// Panics if the wrapped felt exceeds the maximum block height (possible only for values
722    /// that bypassed validation, e.g. a WIT record lifted from a raw felt).
723    #[inline]
724    pub fn as_u32(&self) -> u32 {
725        // Compared in the felt domain: felt comparisons lower to VM intrinsics, which is much
726        // cheaper than u64 comparison libcalls.
727        assert!(
728            self.inner <= Felt::from_u32(u32::MAX),
729            "block number exceeds the maximum block height"
730        );
731        self.inner.as_canonical_u64() as u32
732    }
733
734    /// Returns the block number as a raw [`Felt`] for advanced use.
735    #[inline]
736    pub fn as_felt(&self) -> Felt {
737        self.inner
738    }
739}
740
741impl From<u32> for BlockNumber {
742    fn from(value: u32) -> Self {
743        Self {
744            inner: Felt::from_u32(value),
745        }
746    }
747}
748
749impl TryFrom<Felt> for BlockNumber {
750    type Error = &'static str;
751
752    fn try_from(value: Felt) -> Result<Self, Self::Error> {
753        if value.as_canonical_u64() > u32::MAX as u64 {
754            return Err("block number exceeds the maximum block height");
755        }
756        Ok(Self { inner: value })
757    }
758}
759
760impl From<BlockNumber> for Felt {
761    #[inline]
762    fn from(value: BlockNumber) -> Self {
763        value.inner
764    }
765}
766
767/// The partial hash of a storage slot name.
768///
769/// A slot id consists of two field elements: a `prefix` and a `suffix`.
770///
771/// Slot ids uniquely identify slots in account storage and are used by the host functions exposed
772/// via `miden::protocol::*`.
773#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
774pub struct StorageSlotId {
775    suffix: Felt,
776    prefix: Felt,
777}
778
779impl StorageSlotId {
780    /// Creates a new [`StorageSlotId`] from the provided felts.
781    ///
782    /// Note: this constructor takes `(suffix, prefix)` to match the values returned by
783    /// `miden_protocol::account::StorageSlotId::{suffix,prefix}`.
784    pub fn new(suffix: Felt, prefix: Felt) -> Self {
785        Self { suffix, prefix }
786    }
787
788    /// Creates a new [`StorageSlotId`] from the provided felts in host-call order.
789    ///
790    /// Host functions take the `prefix` first and then the `suffix`.
791    pub fn from_prefix_suffix(prefix: Felt, suffix: Felt) -> Self {
792        Self { suffix, prefix }
793    }
794
795    /// Returns the `(prefix, suffix)` pair in host-call order.
796    pub fn to_prefix_suffix(&self) -> (Felt, Felt) {
797        (self.prefix, self.suffix)
798    }
799
800    /// Returns the `(suffix, prefix)` pair in storage-slot order.
801    pub fn to_suffix_prefix(&self) -> (Felt, Felt) {
802        (self.suffix, self.prefix)
803    }
804
805    /// Returns the suffix of the [`StorageSlotId`].
806    pub fn suffix(&self) -> Felt {
807        self.suffix
808    }
809
810    /// Returns the prefix of the [`StorageSlotId`].
811    pub fn prefix(&self) -> Felt {
812        self.prefix
813    }
814}
815
816#[cfg(test)]
817mod tests {
818    use miden_stdlib_sys::{Felt, Word, felt};
819
820    use super::{
821        AssetAmount, AssetAmountError, BlockNumber, felt_from_padded_word, padded_word_from_felt,
822    };
823
824    /// Ensures `padded_word_from_felt` zero-pads the trailing three limbs.
825    #[test]
826    fn padded_word_from_felt_zero_pads_trailing_limbs() {
827        assert_eq!(
828            padded_word_from_felt(felt!(7)),
829            Word::new([felt!(7), felt!(0), felt!(0), felt!(0)])
830        );
831    }
832
833    /// Ensures `felt_from_padded_word` rejects words with non-zero trailing padding.
834    #[test]
835    fn felt_from_padded_word_rejects_non_zero_padding() {
836        let err =
837            felt_from_padded_word(Word::new([felt!(7), felt!(1), felt!(0), felt!(0)])).unwrap_err();
838
839        assert_eq!(err, "expected zero padding in the trailing three felts");
840    }
841
842    /// Ensures the felt-padding helpers form a lossless roundtrip for scalar values.
843    #[test]
844    fn felt_padding_helpers_roundtrip() {
845        let value = felt!(42);
846
847        assert_eq!(felt_from_padded_word(padded_word_from_felt(value)), Ok(value));
848    }
849
850    /// Ensures amounts within the bound construct successfully and convert back losslessly.
851    #[test]
852    fn asset_amount_valid_amounts() {
853        assert_eq!(AssetAmount::new(0).unwrap().as_u64(), 0);
854        assert_eq!(AssetAmount::new(1000).unwrap().as_u64(), 1000);
855        assert_eq!(AssetAmount::new(AssetAmount::MAX_U64).unwrap(), AssetAmount::max());
856    }
857
858    /// Ensures amounts above the bound are rejected with the offending value.
859    #[test]
860    fn asset_amount_exceeds_max() {
861        assert_eq!(
862            AssetAmount::new(AssetAmount::MAX_U64 + 1),
863            Err(AssetAmountError::AmountTooBig(AssetAmount::MAX_U64 + 1))
864        );
865        assert_eq!(AssetAmount::new(u64::MAX), Err(AssetAmountError::AmountTooBig(u64::MAX)));
866    }
867
868    /// Ensures the maximum amount constant matches its documented value.
869    #[test]
870    fn asset_amount_max_value() {
871        assert_eq!(AssetAmount::MAX_U64, 2u64.pow(63) - 2u64.pow(31));
872        assert_eq!(AssetAmount::max().as_u64(), AssetAmount::MAX_U64);
873    }
874
875    /// Ensures the infallible conversions from small integer types.
876    #[test]
877    fn asset_amount_from_small_types() {
878        assert_eq!(AssetAmount::from(42u8).as_u64(), 42);
879        assert_eq!(AssetAmount::from(1000u16).as_u64(), 1000);
880        assert_eq!(AssetAmount::from(u32::MAX).as_u64(), u32::MAX as u64);
881    }
882
883    /// Ensures the fallible conversions from `u64` and `Felt` enforce the bound.
884    #[test]
885    fn asset_amount_try_from() {
886        assert!(AssetAmount::try_from(AssetAmount::MAX_U64).is_ok());
887        assert!(AssetAmount::try_from(AssetAmount::MAX_U64 + 1).is_err());
888        assert!(AssetAmount::try_from(Felt::new(AssetAmount::MAX_U64).unwrap()).is_ok());
889        assert!(AssetAmount::try_from(Felt::new(AssetAmount::MAX_U64 + 1).unwrap()).is_err());
890        // The largest canonical felt is far above the bound and must be rejected.
891        assert_eq!(
892            AssetAmount::try_from(Felt::new(Felt::ORDER - 1).unwrap()),
893            Err(AssetAmountError::AmountTooBig(Felt::ORDER - 1))
894        );
895    }
896
897    /// Ensures addition computes exact integer sums for in-range amounts.
898    #[test]
899    fn asset_amount_add() {
900        let a = AssetAmount::new(100).unwrap();
901        let b = AssetAmount::new(200).unwrap();
902
903        assert_eq!((a + b).as_u64(), 300);
904        assert_eq!(AssetAmount::ZERO + AssetAmount::ZERO, AssetAmount::ZERO);
905        assert_eq!(AssetAmount::max() + AssetAmount::ZERO, AssetAmount::max());
906    }
907
908    /// Ensures addition panics when the sum exceeds the maximum amount.
909    #[test]
910    #[should_panic(expected = "asset amount addition overflow")]
911    fn asset_amount_add_panics_on_overflow() {
912        let _ = AssetAmount::max() + AssetAmount::new(1).unwrap();
913    }
914
915    /// Ensures addition rejects an out-of-range left operand built via direct field assignment
916    /// instead of laundering it into a valid-looking sum; this operand would wrap the field to
917    /// zero if it were not rejected.
918    #[test]
919    #[should_panic(expected = "asset amount exceeds the maximum allowed amount")]
920    fn asset_amount_add_panics_on_forged_lhs() {
921        let wrapping = AssetAmount {
922            inner: Felt::new(Felt::ORDER - 1).unwrap(),
923        };
924
925        let _ = wrapping + AssetAmount::new(1).unwrap();
926    }
927
928    /// Ensures addition rejects an out-of-range right operand built via direct field
929    /// assignment (reported as an overflowing sum).
930    #[test]
931    #[should_panic(expected = "asset amount addition overflow")]
932    fn asset_amount_add_panics_on_forged_rhs() {
933        let forged = AssetAmount {
934            inner: Felt::new(AssetAmount::MAX_U64 + 1).unwrap(),
935        };
936
937        let _ = AssetAmount::new(1).unwrap() + forged;
938    }
939
940    /// Ensures subtraction computes exact integer differences for in-range amounts.
941    #[test]
942    fn asset_amount_sub() {
943        let a = AssetAmount::new(300).unwrap();
944        let b = AssetAmount::new(100).unwrap();
945
946        assert_eq!((a - b).as_u64(), 200);
947        assert_eq!(AssetAmount::ZERO - AssetAmount::ZERO, AssetAmount::ZERO);
948        assert_eq!(AssetAmount::max() - AssetAmount::max(), AssetAmount::ZERO);
949    }
950
951    /// Ensures subtraction panics when the subtrahend exceeds the minuend.
952    #[test]
953    #[should_panic(expected = "asset amount subtraction underflow")]
954    fn asset_amount_sub_panics_on_underflow() {
955        let _ = AssetAmount::ZERO - AssetAmount::new(1).unwrap();
956    }
957
958    /// Ensures subtraction rejects an out-of-range minuend built via direct field assignment,
959    /// which could otherwise produce an out-of-range result.
960    #[test]
961    #[should_panic(expected = "asset amount exceeds the maximum allowed amount")]
962    fn asset_amount_sub_panics_on_forged_minuend() {
963        let forged = AssetAmount {
964            inner: Felt::new(AssetAmount::MAX_U64 + 1).unwrap(),
965        };
966
967        let _ = forged - AssetAmount::new(1).unwrap();
968    }
969
970    /// Ensures the SDK amount arithmetic agrees with the off-chain protocol implementation
971    /// whenever the protocol operation succeeds (the SDK panics where the protocol errors).
972    #[test]
973    fn asset_amount_differential_vs_protocol() {
974        use miden_protocol::asset::AssetAmount as ProtocolAmount;
975
976        let values = [
977            0u64,
978            1,
979            2,
980            31,
981            u32::MAX as u64,
982            1 << 40,
983            AssetAmount::MAX_U64 / 2,
984            AssetAmount::MAX_U64 - 1,
985            AssetAmount::MAX_U64,
986        ];
987        for &a in &values {
988            for &b in &values {
989                let ours = (AssetAmount::new(a).unwrap(), AssetAmount::new(b).unwrap());
990                let theirs = (ProtocolAmount::new(a).unwrap(), ProtocolAmount::new(b).unwrap());
991
992                if let Ok(sum) = theirs.0 + theirs.1 {
993                    assert_eq!(
994                        (ours.0 + ours.1).as_u64(),
995                        sum.as_u64(),
996                        "sum mismatch for {a} + {b}"
997                    );
998                }
999
1000                if let Ok(difference) = theirs.0 - theirs.1 {
1001                    assert_eq!(
1002                        (ours.0 - ours.1).as_u64(),
1003                        difference.as_u64(),
1004                        "difference mismatch for {a} - {b}"
1005                    );
1006                }
1007            }
1008        }
1009    }
1010
1011    /// Ensures comparison follows canonical integer ordering.
1012    #[test]
1013    fn asset_amount_ordering() {
1014        assert!(AssetAmount::new(1).unwrap() < AssetAmount::new(2).unwrap());
1015        assert!(AssetAmount::max() > AssetAmount::ZERO);
1016        assert_eq!(AssetAmount::default(), AssetAmount::ZERO);
1017    }
1018
1019    /// Ensures the amount displays as a decimal integer.
1020    #[test]
1021    fn asset_amount_display() {
1022        extern crate alloc;
1023        use alloc::string::ToString;
1024
1025        assert_eq!(AssetAmount::new(12345).unwrap().to_string(), "12345");
1026    }
1027
1028    /// Ensures the felt accessor and conversions roundtrip the underlying value.
1029    #[test]
1030    fn asset_amount_felt_roundtrip() {
1031        let amount = AssetAmount::new(500).unwrap();
1032
1033        assert_eq!(amount.as_felt(), felt!(500));
1034        assert_eq!(Felt::from(amount), felt!(500));
1035        assert_eq!(u64::from(amount), 500);
1036    }
1037
1038    /// Ensures block-number felts validate against the `u32` protocol bound.
1039    #[test]
1040    fn block_number_try_from_felt_bounds() {
1041        let max = Felt::new(u32::MAX as u64).unwrap();
1042
1043        assert_eq!(BlockNumber::try_from(max).unwrap().as_u32(), u32::MAX);
1044        assert!(BlockNumber::try_from(Felt::new(u32::MAX as u64 + 1).unwrap()).is_err());
1045    }
1046
1047    /// Ensures `as_u32` refuses to truncate an out-of-range felt smuggled in through the public
1048    /// WIT-record field.
1049    #[test]
1050    #[should_panic(expected = "block number exceeds the maximum block height")]
1051    fn block_number_as_u32_panics_on_out_of_range_felt() {
1052        let forged = BlockNumber {
1053            inner: Felt::new(u32::MAX as u64 + 1).unwrap(),
1054        };
1055
1056        let _ = forged.as_u32();
1057    }
1058}