Skip to main content

miden_protocol/asset/vault/
vault_key.rs

1use alloc::boxed::Box;
2use core::fmt;
3
4use miden_core::LexicographicWord;
5use miden_crypto::merkle::smt::LeafIndex;
6
7use crate::account::AccountId;
8use crate::account::AccountType::{self};
9use crate::asset::vault::AssetId;
10use crate::asset::{Asset, FungibleAsset, NonFungibleAsset};
11use crate::crypto::merkle::smt::SMT_DEPTH;
12use crate::errors::AssetError;
13use crate::{Felt, Word};
14
15/// The unique identifier of an [`Asset`] in the [`AssetVault`](crate::asset::AssetVault).
16///
17/// Its [`Word`] layout is:
18/// ```text
19/// [
20///   asset_id_suffix (64 bits),
21///   asset_id_prefix (64 bits),
22///   faucet_id_suffix (56 bits),
23///   faucet_id_prefix (64 bits)
24/// ]
25/// ```
26///
27/// See the [`Asset`] documentation for the differences between fungible and non-fungible assets.
28#[derive(Debug, PartialEq, Eq, Clone, Copy)]
29pub struct AssetVaultKey {
30    /// The asset ID of the vault key.
31    asset_id: AssetId,
32
33    /// The ID of the faucet that issued the asset.
34    faucet_id: AccountId,
35}
36
37impl AssetVaultKey {
38    /// Creates an [`AssetVaultKey`] from its parts.
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if:
43    /// - the provided ID is not of type
44    ///   [`AccountType::FungibleFaucet`](crate::account::AccountType::FungibleFaucet) or
45    ///   [`AccountType::NonFungibleFaucet`](crate::account::AccountType::NonFungibleFaucet)
46    /// - the asset ID limbs are not zero when `faucet_id` is of type
47    ///   [`AccountType::FungibleFaucet`](crate::account::AccountType::FungibleFaucet).
48    pub fn new(asset_id: AssetId, faucet_id: AccountId) -> Result<Self, AssetError> {
49        if !faucet_id.is_faucet() {
50            return Err(AssetError::InvalidFaucetAccountId(Box::from(format!(
51                "expected account ID of type faucet, found account type {}",
52                faucet_id.account_type()
53            ))));
54        }
55
56        if matches!(faucet_id.account_type(), AccountType::FungibleFaucet) && !asset_id.is_empty() {
57            return Err(AssetError::FungibleAssetIdMustBeZero(asset_id));
58        }
59
60        Ok(Self { asset_id, faucet_id })
61    }
62
63    /// Returns the word representation of the vault key.
64    ///
65    /// See the type-level documentation for details.
66    pub fn to_word(self) -> Word {
67        vault_key_to_word(self.asset_id, self.faucet_id)
68    }
69
70    /// Returns the [`AssetId`] of the vault key that distinguishes different assets issued by the
71    /// same faucet.
72    pub fn asset_id(&self) -> AssetId {
73        self.asset_id
74    }
75
76    /// Returns the [`AccountId`] of the faucet that issued the asset.
77    pub fn faucet_id(&self) -> AccountId {
78        self.faucet_id
79    }
80
81    /// Constructs a fungible asset's key from a faucet ID.
82    ///
83    /// Returns `None` if the provided ID is not of type
84    /// [`AccountType::FungibleFaucet`](crate::account::AccountType::FungibleFaucet)
85    pub fn new_fungible(faucet_id: AccountId) -> Option<Self> {
86        if matches!(faucet_id.account_type(), AccountType::FungibleFaucet) {
87            let asset_id = AssetId::new(Felt::ZERO, Felt::ZERO);
88            Some(
89                Self::new(asset_id, faucet_id)
90                    .expect("we should have account type fungible faucet"),
91            )
92        } else {
93            None
94        }
95    }
96
97    /// Returns the leaf index of a vault key.
98    pub fn to_leaf_index(&self) -> LeafIndex<SMT_DEPTH> {
99        LeafIndex::<SMT_DEPTH>::from(self.to_word())
100    }
101}
102
103// CONVERSIONS
104// ================================================================================================
105
106impl From<AssetVaultKey> for Word {
107    fn from(vault_key: AssetVaultKey) -> Self {
108        vault_key.to_word()
109    }
110}
111
112impl Ord for AssetVaultKey {
113    /// Implements comparison based on [`LexicographicWord`].
114    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
115        LexicographicWord::new(self.to_word()).cmp(&LexicographicWord::new(other.to_word()))
116    }
117}
118
119impl PartialOrd for AssetVaultKey {
120    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
121        Some(self.cmp(other))
122    }
123}
124
125impl TryFrom<Word> for AssetVaultKey {
126    type Error = AssetError;
127
128    /// Attempts to convert the provided [`Word`] into an [`AssetVaultKey`].
129    ///
130    /// # Errors
131    ///
132    /// Returns an error if:
133    /// - the faucet ID in the key is invalid or not of a faucet type.
134    /// - the asset ID limbs are not zero when `faucet_id` is of type
135    ///   [`AccountType::FungibleFaucet`](crate::account::AccountType::FungibleFaucet).
136    fn try_from(key: Word) -> Result<Self, Self::Error> {
137        let asset_id_suffix = key[0];
138        let asset_id_prefix = key[1];
139        let faucet_id_suffix = key[2];
140        let faucet_id_prefix = key[3];
141
142        let asset_id = AssetId::new(asset_id_suffix, asset_id_prefix);
143        let faucet_id = AccountId::try_from_elements(faucet_id_suffix, faucet_id_prefix)
144            .map_err(|err| AssetError::InvalidFaucetAccountId(Box::new(err)))?;
145
146        Self::new(asset_id, faucet_id)
147    }
148}
149
150impl fmt::Display for AssetVaultKey {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        f.write_str(&self.to_word().to_hex())
153    }
154}
155
156impl From<Asset> for AssetVaultKey {
157    fn from(asset: Asset) -> Self {
158        asset.vault_key()
159    }
160}
161
162impl From<FungibleAsset> for AssetVaultKey {
163    fn from(fungible_asset: FungibleAsset) -> Self {
164        fungible_asset.vault_key()
165    }
166}
167
168impl From<NonFungibleAsset> for AssetVaultKey {
169    fn from(non_fungible_asset: NonFungibleAsset) -> Self {
170        non_fungible_asset.vault_key()
171    }
172}
173
174fn vault_key_to_word(asset_id: AssetId, faucet_id: AccountId) -> Word {
175    Word::new([
176        asset_id.suffix(),
177        asset_id.prefix(),
178        faucet_id.suffix(),
179        faucet_id.prefix().as_felt(),
180    ])
181}