miden_objects/asset/vault/
vault_key.rs

1use core::fmt;
2
3use miden_crypto::merkle::LeafIndex;
4use miden_processor::SMT_DEPTH;
5
6use crate::Word;
7use crate::account::AccountType::FungibleFaucet;
8use crate::account::{AccountId, AccountIdPrefix};
9use crate::asset::{Asset, FungibleAsset, NonFungibleAsset};
10
11/// The key of an [`Asset`] in the asset vault.
12///
13/// The layout of an asset key is:
14/// - Fungible asset key: `[0, 0, faucet_id_suffix, faucet_id_prefix]`.
15/// - Non-fungible asset key: `[faucet_id_prefix, hash1, hash2, hash0']`, where `hash0'` is
16///   equivalent to `hash0` with the fungible bit set to `0`. See [`NonFungibleAsset::vault_key`]
17///   for more details.
18///
19/// For details on the layout of an asset, see the documentation of [`Asset`].
20///
21/// ## Guarantees
22///
23/// This type guarantees that it contains a valid fungible or non-fungible asset key:
24/// - For fungible assets
25///   - The felt at index 3 has the fungible bit set to 1 and it is a valid account ID prefix.
26///   - The felt at index 2 is a valid account ID suffix.
27/// - For non-fungible assets
28///   - The felt at index 3 has the fungible bit set to 0.
29///   - The felt at index 0 is a valid account ID prefix.
30///
31/// The fungible bit is the bit in the [`AccountId`] that encodes whether the ID is a faucet.
32#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
33pub struct AssetVaultKey(Word);
34
35impl AssetVaultKey {
36    /// Creates a new [`AssetVaultKey`] from the given [`Word`] **without performing validation**.
37    ///
38    /// ## Warning
39    ///
40    /// This function **does not check** whether the provided `Word` represents a valid
41    /// fungible or non-fungible asset key.
42    pub fn new_unchecked(value: Word) -> Self {
43        Self(value)
44    }
45
46    /// Returns an [`AccountIdPrefix`] from the asset key.
47    pub fn faucet_id_prefix(&self) -> AccountIdPrefix {
48        if self.is_fungible() {
49            AccountIdPrefix::new_unchecked(self.0[3])
50        } else {
51            AccountIdPrefix::new_unchecked(self.0[0])
52        }
53    }
54
55    /// Returns the [`AccountId`] from the asset key if it is a fungible asset, `None` otherwise.
56    pub fn faucet_id(&self) -> Option<AccountId> {
57        if self.is_fungible() {
58            Some(AccountId::new_unchecked([self.0[3], self.0[2]]))
59        } else {
60            None
61        }
62    }
63
64    /// Returns the leaf index of a vault key.
65    pub fn to_leaf_index(&self) -> LeafIndex<SMT_DEPTH> {
66        LeafIndex::<SMT_DEPTH>::from(self.0)
67    }
68
69    /// Constructs a fungible asset's key from a faucet ID.
70    ///
71    /// Returns `None` if the provided ID is not of type
72    /// [`AccountType::FungibleFaucet`](crate::account::AccountType::FungibleFaucet)
73    pub fn from_account_id(faucet_id: AccountId) -> Option<Self> {
74        match faucet_id.account_type() {
75            FungibleFaucet => {
76                let mut key = Word::empty();
77                key[2] = faucet_id.suffix();
78                key[3] = faucet_id.prefix().as_felt();
79                Some(AssetVaultKey::new_unchecked(key))
80            },
81            _ => None,
82        }
83    }
84
85    /// Returns `true` if the asset key is for a fungible asset, `false` otherwise.
86    fn is_fungible(&self) -> bool {
87        self.0[0].as_int() == 0 && self.0[1].as_int() == 0
88    }
89}
90
91impl fmt::Display for AssetVaultKey {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "{}", self.0)
94    }
95}
96
97// CONVERSIONS
98// ================================================================================================
99
100impl From<AssetVaultKey> for Word {
101    fn from(vault_key: AssetVaultKey) -> Self {
102        vault_key.0
103    }
104}
105
106impl From<Asset> for AssetVaultKey {
107    fn from(asset: Asset) -> Self {
108        asset.vault_key()
109    }
110}
111
112impl From<FungibleAsset> for AssetVaultKey {
113    fn from(fungible_asset: FungibleAsset) -> Self {
114        fungible_asset.vault_key()
115    }
116}
117
118impl From<NonFungibleAsset> for AssetVaultKey {
119    fn from(non_fungible_asset: NonFungibleAsset) -> Self {
120        non_fungible_asset.vault_key()
121    }
122}
123
124// TESTS
125// ================================================================================================
126
127#[cfg(test)]
128mod tests {
129    use miden_core::Felt;
130
131    use super::*;
132    use crate::account::{AccountIdVersion, AccountStorageMode, AccountType};
133
134    fn make_non_fungible_key(prefix: u64) -> AssetVaultKey {
135        let word = [Felt::new(prefix), Felt::new(11), Felt::new(22), Felt::new(33)].into();
136        AssetVaultKey::new_unchecked(word)
137    }
138
139    #[test]
140    fn test_faucet_id_for_fungible_asset() {
141        let id = AccountId::dummy(
142            [0xff; 15],
143            AccountIdVersion::Version0,
144            AccountType::FungibleFaucet,
145            AccountStorageMode::Public,
146        );
147
148        let key =
149            AssetVaultKey::from_account_id(id).expect("Expected AssetVaultKey for FungibleFaucet");
150
151        // faucet_id_prefix() should match AccountId prefix
152        assert_eq!(key.faucet_id_prefix(), id.prefix());
153
154        // faucet_id() should return the same account id
155        assert_eq!(key.faucet_id().unwrap(), id);
156    }
157
158    #[test]
159    fn test_faucet_id_for_non_fungible_asset() {
160        let id = AccountId::dummy(
161            [0xff; 15],
162            AccountIdVersion::Version0,
163            AccountType::NonFungibleFaucet,
164            AccountStorageMode::Public,
165        );
166
167        let prefix_value = id.prefix().as_u64();
168        let key = make_non_fungible_key(prefix_value);
169
170        // faucet_id_prefix() should match AccountId prefix
171        assert_eq!(key.faucet_id_prefix(), id.prefix());
172
173        // faucet_id() should return the None
174        assert_eq!(key.faucet_id(), None);
175    }
176}