Skip to main content

miden_protocol/asset/vault/
vault_key.rs

1use core::fmt;
2
3use miden_crypto::merkle::smt::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 a reference to the inner [Word] of this key.
86    pub fn as_word(&self) -> &Word {
87        &self.0
88    }
89
90    /// Returns `true` if the asset key is for a fungible asset, `false` otherwise.
91    fn is_fungible(&self) -> bool {
92        self.0[0].as_int() == 0 && self.0[1].as_int() == 0
93    }
94}
95
96impl fmt::Display for AssetVaultKey {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        write!(f, "{}", self.0)
99    }
100}
101
102// CONVERSIONS
103// ================================================================================================
104
105impl From<AssetVaultKey> for Word {
106    fn from(vault_key: AssetVaultKey) -> Self {
107        vault_key.0
108    }
109}
110
111impl From<Asset> for AssetVaultKey {
112    fn from(asset: Asset) -> Self {
113        asset.vault_key()
114    }
115}
116
117impl From<FungibleAsset> for AssetVaultKey {
118    fn from(fungible_asset: FungibleAsset) -> Self {
119        fungible_asset.vault_key()
120    }
121}
122
123impl From<NonFungibleAsset> for AssetVaultKey {
124    fn from(non_fungible_asset: NonFungibleAsset) -> Self {
125        non_fungible_asset.vault_key()
126    }
127}
128
129// TESTS
130// ================================================================================================
131
132#[cfg(test)]
133mod tests {
134    use miden_core::Felt;
135
136    use super::*;
137    use crate::account::{AccountIdVersion, AccountStorageMode, AccountType};
138
139    fn make_non_fungible_key(prefix: u64) -> AssetVaultKey {
140        let word = [Felt::new(prefix), Felt::new(11), Felt::new(22), Felt::new(33)].into();
141        AssetVaultKey::new_unchecked(word)
142    }
143
144    #[test]
145    fn test_faucet_id_for_fungible_asset() {
146        let id = AccountId::dummy(
147            [0xff; 15],
148            AccountIdVersion::Version0,
149            AccountType::FungibleFaucet,
150            AccountStorageMode::Public,
151        );
152
153        let key =
154            AssetVaultKey::from_account_id(id).expect("Expected AssetVaultKey for FungibleFaucet");
155
156        // faucet_id_prefix() should match AccountId prefix
157        assert_eq!(key.faucet_id_prefix(), id.prefix());
158
159        // faucet_id() should return the same account id
160        assert_eq!(key.faucet_id().unwrap(), id);
161    }
162
163    #[test]
164    fn test_faucet_id_for_non_fungible_asset() {
165        let id = AccountId::dummy(
166            [0xff; 15],
167            AccountIdVersion::Version0,
168            AccountType::NonFungibleFaucet,
169            AccountStorageMode::Public,
170        );
171
172        let prefix_value = id.prefix().as_u64();
173        let key = make_non_fungible_key(prefix_value);
174
175        // faucet_id_prefix() should match AccountId prefix
176        assert_eq!(key.faucet_id_prefix(), id.prefix());
177
178        // faucet_id() should return the None
179        assert_eq!(key.faucet_id(), None);
180    }
181}