1extern crate alloc;
2
3use miden_field_repr::{FromFeltRepr, ToFeltRepr};
4use miden_stdlib_sys::{Felt, Word, felt};
5
6pub fn padded_word_from_felt(value: Felt) -> Word {
8 Word::new([value, felt!(0), felt!(0), felt!(0)])
9}
10
11pub 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#[derive(Copy, Clone, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
22pub struct AccountId {
23 pub prefix: Felt,
24 pub suffix: Felt,
25}
26
27impl AccountId {
28 pub fn new(prefix: Felt, suffix: Felt) -> Self {
30 Self { prefix, suffix }
31 }
32}
33
34#[derive(Copy, Clone)]
37#[repr(C)]
38pub(crate) struct RawAccountId {
39 pub suffix: Felt,
40 pub prefix: Felt,
41}
42
43impl RawAccountId {
44 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#[derive(Copy, Clone, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
78#[repr(C)]
79pub struct Asset {
80 pub key: Word,
82 pub value: Word,
84}
85
86impl Asset {
87 pub fn new(key: impl Into<Word>, value: impl Into<Word>) -> Self {
89 Self {
90 key: key.into(),
91 value: value.into(),
92 }
93 }
94
95 pub fn amount(&self) -> AssetAmount {
105 assert!(self.is_fungible(), "asset is not fungible");
106 let amount = self.value[0];
107 assert!(
108 amount <= AssetAmount::max_inner(),
109 "asset amount exceeds the maximum allowed amount"
110 );
111 AssetAmount { inner: amount }
112 }
113
114 #[inline]
120 pub fn is_fungible(&self) -> bool {
121 self.key[2].as_canonical_u64() & 1 == 1
126 }
127}
128
129impl From<Asset> for (Word, Word) {
130 fn from(val: Asset) -> Self {
131 (val.key, val.value)
132 }
133}
134
135#[derive(Copy, Clone, Debug, PartialEq, Eq)]
137pub enum AssetAmountError {
138 AmountTooBig(u64),
140}
141
142impl core::fmt::Display for AssetAmountError {
143 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
144 match self {
145 Self::AmountTooBig(amount) => {
146 write!(f, "asset amount {amount} exceeds the maximum {}", AssetAmount::MAX_U64)
147 }
148 }
149 }
150}
151
152impl core::error::Error for AssetAmountError {}
153
154#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
168#[repr(transparent)]
169pub struct AssetAmount {
170 #[doc(hidden)]
176 pub inner: Felt,
177}
178
179impl AssetAmount {
180 pub const MAX_U64: u64 = (1u64 << 63) - (1u64 << 31);
187 pub const ZERO: Self = Self { inner: Felt::ZERO };
189
190 #[inline]
192 pub fn max() -> Self {
193 Self {
194 inner: Self::max_inner(),
195 }
196 }
197
198 pub fn new(amount: u64) -> Result<Self, AssetAmountError> {
204 if amount > Self::MAX_U64 {
205 return Err(AssetAmountError::AmountTooBig(amount));
206 }
207 Ok(Self {
209 inner: Felt::new_unchecked(amount),
210 })
211 }
212
213 #[inline]
215 pub fn as_u64(&self) -> u64 {
216 self.inner.as_canonical_u64()
217 }
218
219 #[inline]
221 pub fn as_felt(&self) -> Felt {
222 self.inner
223 }
224
225 #[inline(always)]
227 fn max_inner() -> Felt {
228 Felt::new_unchecked(Self::MAX_U64)
230 }
231
232 #[inline]
234 fn amount_too_big(value: Felt) -> AssetAmountError {
235 AssetAmountError::AmountTooBig(value.as_canonical_u64())
237 }
238}
239
240const _: () = assert!(AssetAmount::MAX_U64 * 2 == Felt::ORDER - 1);
243
244impl core::ops::Add for AssetAmount {
245 type Output = Self;
246
247 fn add(self, other: Self) -> Self {
253 let max = Self::max_inner();
254 assert!(self.inner <= max, "asset amount exceeds the maximum allowed amount");
257 let headroom = max - self.inner;
262 assert!(other.inner <= headroom, "asset amount addition overflow");
263 Self {
264 inner: self.inner + other.inner,
265 }
266 }
267}
268
269impl core::ops::Sub for AssetAmount {
270 type Output = Self;
271
272 fn sub(self, other: Self) -> Self {
279 let max = Self::max_inner();
280 assert!(self.inner <= max, "asset amount exceeds the maximum allowed amount");
283 assert!(other.inner <= self.inner, "asset amount subtraction underflow");
286 Self {
287 inner: self.inner - other.inner,
288 }
289 }
290}
291
292impl Default for AssetAmount {
293 fn default() -> Self {
294 Self::ZERO
295 }
296}
297
298impl From<u8> for AssetAmount {
299 fn from(value: u8) -> Self {
300 Self {
301 inner: Felt::from(value),
302 }
303 }
304}
305
306impl From<u16> for AssetAmount {
307 fn from(value: u16) -> Self {
308 Self {
309 inner: Felt::from(value),
310 }
311 }
312}
313
314impl From<u32> for AssetAmount {
315 fn from(value: u32) -> Self {
316 Self {
318 inner: Felt::from_u32(value),
319 }
320 }
321}
322
323impl TryFrom<u64> for AssetAmount {
324 type Error = AssetAmountError;
325
326 fn try_from(value: u64) -> Result<Self, Self::Error> {
327 Self::new(value)
328 }
329}
330
331impl TryFrom<Felt> for AssetAmount {
332 type Error = AssetAmountError;
333
334 fn try_from(value: Felt) -> Result<Self, Self::Error> {
335 if value > Self::max_inner() {
336 return Err(Self::amount_too_big(value));
337 }
338 Ok(Self { inner: value })
339 }
340}
341
342impl From<AssetAmount> for u64 {
343 fn from(amount: AssetAmount) -> Self {
344 amount.as_u64()
345 }
346}
347
348impl From<AssetAmount> for Felt {
349 fn from(amount: AssetAmount) -> Self {
350 amount.inner
351 }
352}
353
354impl core::fmt::Display for AssetAmount {
355 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
356 write!(f, "{}", self.as_u64())
357 }
358}
359
360#[derive(Clone, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
362#[repr(transparent)]
363pub struct Recipient {
364 pub inner: Word,
365}
366
367#[derive(Copy, Clone, Debug, PartialEq, Eq)]
373#[repr(C)]
374pub struct NoteMetadata {
375 pub header: Word,
377}
378
379impl NoteMetadata {
380 pub fn new(header: Word) -> Self {
382 Self { header }
383 }
384}
385
386#[derive(Copy, Clone)]
388#[repr(C)]
389pub(crate) struct RawAttachmentLocation {
390 pub is_found: Felt,
392 pub index: Felt,
394}
395
396impl RawAttachmentLocation {
397 pub(crate) fn into_attachment_index(self) -> Option<u32> {
399 if self.is_found == Felt::ZERO {
400 return None;
401 }
402 Some(self.index.as_canonical_u64() as u32)
404 }
405}
406
407impl From<[Felt; 4]> for Recipient {
408 fn from(value: [Felt; 4]) -> Self {
409 Recipient {
410 inner: Word::from(value),
411 }
412 }
413}
414
415impl From<Word> for Recipient {
416 fn from(value: Word) -> Self {
417 Recipient { inner: value }
418 }
419}
420
421impl From<Recipient> for Word {
422 #[inline]
423 fn from(value: Recipient) -> Self {
424 value.inner
425 }
426}
427
428#[derive(Clone, Copy, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
429#[repr(transparent)]
430pub struct Tag {
431 pub inner: Felt,
432}
433
434impl From<Felt> for Tag {
435 fn from(value: Felt) -> Self {
436 Tag { inner: value }
437 }
438}
439
440impl From<Tag> for Word {
441 #[inline]
442 fn from(value: Tag) -> Self {
443 padded_word_from_felt(value.inner)
444 }
445}
446
447impl TryFrom<Word> for Tag {
448 type Error = &'static str;
449
450 #[inline]
451 fn try_from(value: Word) -> Result<Self, Self::Error> {
452 Ok(Tag {
453 inner: felt_from_padded_word(value)?,
454 })
455 }
456}
457
458#[derive(Clone, Copy, Debug, PartialEq, Eq)]
459#[repr(transparent)]
460pub struct NoteIdx {
461 pub inner: Felt,
462}
463
464impl From<NoteIdx> for Word {
465 #[inline]
466 fn from(value: NoteIdx) -> Self {
467 padded_word_from_felt(value.inner)
468 }
469}
470
471impl TryFrom<Word> for NoteIdx {
472 type Error = &'static str;
473
474 #[inline]
475 fn try_from(value: Word) -> Result<Self, Self::Error> {
476 Ok(NoteIdx {
477 inner: felt_from_padded_word(value)?,
478 })
479 }
480}
481
482#[derive(Clone, Copy, Debug, PartialEq, Eq, FromFeltRepr, ToFeltRepr)]
483#[repr(transparent)]
484pub struct NoteType {
485 pub inner: Felt,
486}
487
488impl From<Felt> for NoteType {
489 fn from(value: Felt) -> Self {
490 NoteType { inner: value }
491 }
492}
493
494impl From<NoteType> for Word {
495 #[inline]
496 fn from(value: NoteType) -> Self {
497 padded_word_from_felt(value.inner)
498 }
499}
500
501impl TryFrom<Word> for NoteType {
502 type Error = &'static str;
503
504 #[inline]
505 fn try_from(value: Word) -> Result<Self, Self::Error> {
506 Ok(NoteType {
507 inner: felt_from_padded_word(value)?,
508 })
509 }
510}
511
512#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
518#[repr(transparent)]
519pub struct Nonce {
520 #[doc(hidden)]
523 pub inner: Felt,
524}
525
526impl Nonce {
527 #[inline]
529 pub fn as_u64(&self) -> u64 {
530 self.inner.as_canonical_u64()
531 }
532
533 #[inline]
535 pub fn as_felt(&self) -> Felt {
536 self.inner
537 }
538}
539
540impl From<Nonce> for Felt {
541 #[inline]
542 fn from(value: Nonce) -> Self {
543 value.inner
544 }
545}
546
547#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
553#[repr(transparent)]
554pub struct BlockNumber {
555 #[doc(hidden)]
558 pub inner: Felt,
559}
560
561impl BlockNumber {
562 #[inline]
569 pub fn as_u32(&self) -> u32 {
570 assert!(
573 self.inner <= Felt::from_u32(u32::MAX),
574 "block number exceeds the maximum block height"
575 );
576 self.inner.as_canonical_u64() as u32
577 }
578
579 #[inline]
581 pub fn as_felt(&self) -> Felt {
582 self.inner
583 }
584}
585
586impl From<u32> for BlockNumber {
587 fn from(value: u32) -> Self {
588 Self {
589 inner: Felt::from_u32(value),
590 }
591 }
592}
593
594impl TryFrom<Felt> for BlockNumber {
595 type Error = &'static str;
596
597 fn try_from(value: Felt) -> Result<Self, Self::Error> {
598 if value.as_canonical_u64() > u32::MAX as u64 {
599 return Err("block number exceeds the maximum block height");
600 }
601 Ok(Self { inner: value })
602 }
603}
604
605impl From<BlockNumber> for Felt {
606 #[inline]
607 fn from(value: BlockNumber) -> Self {
608 value.inner
609 }
610}
611
612#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
619pub struct StorageSlotId {
620 suffix: Felt,
621 prefix: Felt,
622}
623
624impl StorageSlotId {
625 pub fn new(suffix: Felt, prefix: Felt) -> Self {
630 Self { suffix, prefix }
631 }
632
633 pub fn from_prefix_suffix(prefix: Felt, suffix: Felt) -> Self {
637 Self { suffix, prefix }
638 }
639
640 pub fn to_prefix_suffix(&self) -> (Felt, Felt) {
642 (self.prefix, self.suffix)
643 }
644
645 pub fn to_suffix_prefix(&self) -> (Felt, Felt) {
647 (self.suffix, self.prefix)
648 }
649
650 pub fn suffix(&self) -> Felt {
652 self.suffix
653 }
654
655 pub fn prefix(&self) -> Felt {
657 self.prefix
658 }
659}
660
661#[cfg(test)]
662mod tests {
663 use miden_stdlib_sys::{Felt, Word, felt};
664
665 use super::{
666 Asset, AssetAmount, AssetAmountError, BlockNumber, felt_from_padded_word,
667 padded_word_from_felt,
668 };
669
670 #[test]
672 fn padded_word_from_felt_zero_pads_trailing_limbs() {
673 assert_eq!(
674 padded_word_from_felt(felt!(7)),
675 Word::new([felt!(7), felt!(0), felt!(0), felt!(0)])
676 );
677 }
678
679 #[test]
681 fn felt_from_padded_word_rejects_non_zero_padding() {
682 let err =
683 felt_from_padded_word(Word::new([felt!(7), felt!(1), felt!(0), felt!(0)])).unwrap_err();
684
685 assert_eq!(err, "expected zero padding in the trailing three felts");
686 }
687
688 #[test]
690 fn felt_padding_helpers_roundtrip() {
691 let value = felt!(42);
692
693 assert_eq!(felt_from_padded_word(padded_word_from_felt(value)), Ok(value));
694 }
695
696 #[test]
698 fn asset_amount_valid_amounts() {
699 assert_eq!(AssetAmount::new(0).unwrap().as_u64(), 0);
700 assert_eq!(AssetAmount::new(1000).unwrap().as_u64(), 1000);
701 assert_eq!(AssetAmount::new(AssetAmount::MAX_U64).unwrap(), AssetAmount::max());
702 }
703
704 #[test]
706 fn asset_amount_exceeds_max() {
707 assert_eq!(
708 AssetAmount::new(AssetAmount::MAX_U64 + 1),
709 Err(AssetAmountError::AmountTooBig(AssetAmount::MAX_U64 + 1))
710 );
711 assert_eq!(AssetAmount::new(u64::MAX), Err(AssetAmountError::AmountTooBig(u64::MAX)));
712 }
713
714 #[test]
716 fn asset_amount_max_value() {
717 assert_eq!(AssetAmount::MAX_U64, 2u64.pow(63) - 2u64.pow(31));
718 assert_eq!(AssetAmount::max().as_u64(), AssetAmount::MAX_U64);
719 }
720
721 #[test]
723 fn asset_amount_from_small_types() {
724 assert_eq!(AssetAmount::from(42u8).as_u64(), 42);
725 assert_eq!(AssetAmount::from(1000u16).as_u64(), 1000);
726 assert_eq!(AssetAmount::from(u32::MAX).as_u64(), u32::MAX as u64);
727 }
728
729 #[test]
731 fn asset_amount_try_from() {
732 assert!(AssetAmount::try_from(AssetAmount::MAX_U64).is_ok());
733 assert!(AssetAmount::try_from(AssetAmount::MAX_U64 + 1).is_err());
734 assert!(AssetAmount::try_from(Felt::new(AssetAmount::MAX_U64).unwrap()).is_ok());
735 assert!(AssetAmount::try_from(Felt::new(AssetAmount::MAX_U64 + 1).unwrap()).is_err());
736 assert_eq!(
738 AssetAmount::try_from(Felt::new(Felt::ORDER - 1).unwrap()),
739 Err(AssetAmountError::AmountTooBig(Felt::ORDER - 1))
740 );
741 }
742
743 #[test]
745 fn asset_amount_add() {
746 let a = AssetAmount::new(100).unwrap();
747 let b = AssetAmount::new(200).unwrap();
748
749 assert_eq!((a + b).as_u64(), 300);
750 assert_eq!(AssetAmount::ZERO + AssetAmount::ZERO, AssetAmount::ZERO);
751 assert_eq!(AssetAmount::max() + AssetAmount::ZERO, AssetAmount::max());
752 }
753
754 #[test]
756 #[should_panic(expected = "asset amount addition overflow")]
757 fn asset_amount_add_panics_on_overflow() {
758 let _ = AssetAmount::max() + AssetAmount::new(1).unwrap();
759 }
760
761 #[test]
765 #[should_panic(expected = "asset amount exceeds the maximum allowed amount")]
766 fn asset_amount_add_panics_on_forged_lhs() {
767 let wrapping = AssetAmount {
768 inner: Felt::new(Felt::ORDER - 1).unwrap(),
769 };
770
771 let _ = wrapping + AssetAmount::new(1).unwrap();
772 }
773
774 #[test]
777 #[should_panic(expected = "asset amount addition overflow")]
778 fn asset_amount_add_panics_on_forged_rhs() {
779 let forged = AssetAmount {
780 inner: Felt::new(AssetAmount::MAX_U64 + 1).unwrap(),
781 };
782
783 let _ = AssetAmount::new(1).unwrap() + forged;
784 }
785
786 #[test]
788 fn asset_amount_sub() {
789 let a = AssetAmount::new(300).unwrap();
790 let b = AssetAmount::new(100).unwrap();
791
792 assert_eq!((a - b).as_u64(), 200);
793 assert_eq!(AssetAmount::ZERO - AssetAmount::ZERO, AssetAmount::ZERO);
794 assert_eq!(AssetAmount::max() - AssetAmount::max(), AssetAmount::ZERO);
795 }
796
797 #[test]
799 #[should_panic(expected = "asset amount subtraction underflow")]
800 fn asset_amount_sub_panics_on_underflow() {
801 let _ = AssetAmount::ZERO - AssetAmount::new(1).unwrap();
802 }
803
804 #[test]
807 #[should_panic(expected = "asset amount exceeds the maximum allowed amount")]
808 fn asset_amount_sub_panics_on_forged_minuend() {
809 let forged = AssetAmount {
810 inner: Felt::new(AssetAmount::MAX_U64 + 1).unwrap(),
811 };
812
813 let _ = forged - AssetAmount::new(1).unwrap();
814 }
815
816 #[test]
819 fn asset_amount_differential_vs_protocol() {
820 use miden_protocol::asset::AssetAmount as ProtocolAmount;
821
822 let values = [
823 0u64,
824 1,
825 2,
826 31,
827 u32::MAX as u64,
828 1 << 40,
829 AssetAmount::MAX_U64 / 2,
830 AssetAmount::MAX_U64 - 1,
831 AssetAmount::MAX_U64,
832 ];
833 for &a in &values {
834 for &b in &values {
835 let ours = (AssetAmount::new(a).unwrap(), AssetAmount::new(b).unwrap());
836 let theirs = (ProtocolAmount::new(a).unwrap(), ProtocolAmount::new(b).unwrap());
837
838 if let Ok(sum) = theirs.0 + theirs.1 {
839 assert_eq!(
840 (ours.0 + ours.1).as_u64(),
841 sum.as_u64(),
842 "sum mismatch for {a} + {b}"
843 );
844 }
845
846 if let Ok(difference) = theirs.0 - theirs.1 {
847 assert_eq!(
848 (ours.0 - ours.1).as_u64(),
849 difference.as_u64(),
850 "difference mismatch for {a} - {b}"
851 );
852 }
853 }
854 }
855 }
856
857 #[test]
859 fn asset_amount_ordering() {
860 assert!(AssetAmount::new(1).unwrap() < AssetAmount::new(2).unwrap());
861 assert!(AssetAmount::max() > AssetAmount::ZERO);
862 assert_eq!(AssetAmount::default(), AssetAmount::ZERO);
863 }
864
865 #[test]
867 fn asset_amount_display() {
868 extern crate alloc;
869 use alloc::string::ToString;
870
871 assert_eq!(AssetAmount::new(12345).unwrap().to_string(), "12345");
872 }
873
874 #[test]
876 fn asset_amount_felt_roundtrip() {
877 let amount = AssetAmount::new(500).unwrap();
878
879 assert_eq!(amount.as_felt(), felt!(500));
880 assert_eq!(Felt::from(amount), felt!(500));
881 assert_eq!(u64::from(amount), 500);
882 }
883
884 fn fungible_asset(amount: Felt) -> Asset {
887 Asset::new(
888 Word::new([felt!(0), felt!(0), felt!(1), felt!(0)]),
889 Word::new([amount, felt!(0), felt!(0), felt!(0)]),
890 )
891 }
892
893 #[test]
895 fn asset_is_fungible() {
896 let non_fungible = Asset::new(
897 Word::new([felt!(0), felt!(0), felt!(2), felt!(0)]),
898 Word::new([felt!(42), felt!(0), felt!(0), felt!(0)]),
899 );
900
901 assert!(fungible_asset(felt!(42)).is_fungible());
902 assert!(!non_fungible.is_fungible());
903 }
904
905 #[test]
907 fn asset_amount_decodes_valid_fungible_assets() {
908 let asset = fungible_asset(felt!(42));
909 let callback_asset =
911 Asset::new(Word::new([felt!(0), felt!(0), felt!(5), felt!(0)]), asset.value);
912
913 assert_eq!(asset.amount(), AssetAmount::new(42).unwrap());
914 assert_eq!(callback_asset.amount(), AssetAmount::new(42).unwrap());
915 }
916
917 #[test]
919 #[should_panic(expected = "asset is not fungible")]
920 fn asset_amount_panics_on_non_fungible() {
921 let non_fungible = Asset::new(
922 Word::new([felt!(1), felt!(0), felt!(0), felt!(0)]),
923 Word::new([felt!(42), felt!(0), felt!(0), felt!(0)]),
924 );
925
926 let _ = non_fungible.amount();
927 }
928
929 #[test]
931 #[should_panic(expected = "asset amount exceeds the maximum allowed amount")]
932 fn asset_amount_panics_on_excessive_amount() {
933 let excessive_amount = fungible_asset(Felt::new(AssetAmount::MAX_U64 + 1).unwrap());
934
935 let _ = excessive_amount.amount();
936 }
937
938 #[test]
940 fn block_number_try_from_felt_bounds() {
941 let max = Felt::new(u32::MAX as u64).unwrap();
942
943 assert_eq!(BlockNumber::try_from(max).unwrap().as_u32(), u32::MAX);
944 assert!(BlockNumber::try_from(Felt::new(u32::MAX as u64 + 1).unwrap()).is_err());
945 }
946
947 #[test]
950 #[should_panic(expected = "block number exceeds the maximum block height")]
951 fn block_number_as_u32_panics_on_out_of_range_felt() {
952 let forged = BlockNumber {
953 inner: Felt::new(u32::MAX as u64 + 1).unwrap(),
954 };
955
956 let _ = forged.as_u32();
957 }
958}