miden_protocol/asset/vault/
vault_key.rs1use alloc::boxed::Box;
2use alloc::string::ToString;
3use core::fmt;
4
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, AssetCallbackFlag, FungibleAsset, NonFungibleAsset};
11use crate::crypto::merkle::smt::SMT_DEPTH;
12use crate::errors::AssetError;
13use crate::utils::serde::{
14 ByteReader,
15 ByteWriter,
16 Deserializable,
17 DeserializationError,
18 Serializable,
19};
20use crate::{Felt, Word};
21
22#[derive(Debug, PartialEq, Eq, Clone, Copy)]
34pub struct AssetVaultKey {
35 asset_id: AssetId,
37
38 faucet_id: AccountId,
40
41 callback_flag: AssetCallbackFlag,
43}
44
45impl AssetVaultKey {
46 pub const SERIALIZED_SIZE: usize = Word::SERIALIZED_SIZE;
50
51 pub fn new_native(asset_id: AssetId, faucet_id: AccountId) -> Result<Self, AssetError> {
65 Self::new(asset_id, faucet_id, AssetCallbackFlag::Disabled)
66 }
67
68 pub fn new(
79 asset_id: AssetId,
80 faucet_id: AccountId,
81 callback_flag: AssetCallbackFlag,
82 ) -> Result<Self, AssetError> {
83 if !faucet_id.is_faucet() {
84 return Err(AssetError::InvalidFaucetAccountId(Box::from(format!(
85 "expected account ID of type faucet, found account type {}",
86 faucet_id.account_type()
87 ))));
88 }
89
90 if matches!(faucet_id.account_type(), AccountType::FungibleFaucet) && !asset_id.is_empty() {
91 return Err(AssetError::FungibleAssetIdMustBeZero(asset_id));
92 }
93
94 Ok(Self { asset_id, faucet_id, callback_flag })
95 }
96
97 pub fn to_word(&self) -> Word {
104 let faucet_suffix = self.faucet_id.suffix().as_canonical_u64();
105 debug_assert!(faucet_suffix & 0xff == 0, "lower 8 bits of faucet suffix must be zero");
108 let faucet_id_suffix_and_metadata = faucet_suffix | self.callback_flag.as_u8() as u64;
109 let faucet_id_suffix_and_metadata = Felt::try_from(faucet_id_suffix_and_metadata)
110 .expect("highest bit should still be zero resulting in a valid felt");
111
112 Word::new([
113 self.asset_id.suffix(),
114 self.asset_id.prefix(),
115 faucet_id_suffix_and_metadata,
116 self.faucet_id.prefix().as_felt(),
117 ])
118 }
119
120 pub fn asset_id(&self) -> AssetId {
123 self.asset_id
124 }
125
126 pub fn faucet_id(&self) -> AccountId {
128 self.faucet_id
129 }
130
131 pub fn callback_flag(&self) -> AssetCallbackFlag {
133 self.callback_flag
134 }
135
136 pub fn new_fungible(faucet_id: AccountId) -> Option<Self> {
141 if matches!(faucet_id.account_type(), AccountType::FungibleFaucet) {
142 let asset_id = AssetId::new(Felt::ZERO, Felt::ZERO);
143 Some(
144 Self::new_native(asset_id, faucet_id)
145 .expect("we should have account type fungible faucet"),
146 )
147 } else {
148 None
149 }
150 }
151
152 pub fn to_leaf_index(&self) -> LeafIndex<SMT_DEPTH> {
154 LeafIndex::<SMT_DEPTH>::from(self.to_word())
155 }
156}
157
158impl From<AssetVaultKey> for Word {
162 fn from(vault_key: AssetVaultKey) -> Self {
163 vault_key.to_word()
164 }
165}
166
167impl Ord for AssetVaultKey {
168 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
170 self.to_word().cmp(&other.to_word())
171 }
172}
173
174impl PartialOrd for AssetVaultKey {
175 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
176 Some(self.cmp(other))
177 }
178}
179
180impl TryFrom<Word> for AssetVaultKey {
181 type Error = AssetError;
182
183 fn try_from(key: Word) -> Result<Self, Self::Error> {
192 let asset_id_suffix = key[0];
193 let asset_id_prefix = key[1];
194 let faucet_id_suffix_and_metadata = key[2];
195 let faucet_id_prefix = key[3];
196
197 let raw = faucet_id_suffix_and_metadata.as_canonical_u64();
198 let callback_flag = AssetCallbackFlag::try_from((raw & 0xff) as u8)?;
199 let faucet_id_suffix = Felt::try_from(raw & 0xffff_ffff_ffff_ff00)
200 .expect("clearing lower bits should not produce an invalid felt");
201
202 let asset_id = AssetId::new(asset_id_suffix, asset_id_prefix);
203 let faucet_id = AccountId::try_from_elements(faucet_id_suffix, faucet_id_prefix)
204 .map_err(|err| AssetError::InvalidFaucetAccountId(Box::new(err)))?;
205
206 Self::new(asset_id, faucet_id, callback_flag)
207 }
208}
209
210impl fmt::Display for AssetVaultKey {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 f.write_str(&self.to_word().to_hex())
213 }
214}
215
216impl From<Asset> for AssetVaultKey {
217 fn from(asset: Asset) -> Self {
218 asset.vault_key()
219 }
220}
221
222impl From<FungibleAsset> for AssetVaultKey {
223 fn from(fungible_asset: FungibleAsset) -> Self {
224 fungible_asset.vault_key()
225 }
226}
227
228impl From<NonFungibleAsset> for AssetVaultKey {
229 fn from(non_fungible_asset: NonFungibleAsset) -> Self {
230 non_fungible_asset.vault_key()
231 }
232}
233
234impl Serializable for AssetVaultKey {
238 fn write_into<W: ByteWriter>(&self, target: &mut W) {
239 self.to_word().write_into(target);
240 }
241
242 fn get_size_hint(&self) -> usize {
243 Self::SERIALIZED_SIZE
244 }
245}
246
247impl Deserializable for AssetVaultKey {
248 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
249 let word: Word = source.read()?;
250 Self::try_from(word).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
251 }
252}
253
254#[cfg(test)]
258mod tests {
259 use super::*;
260 use crate::asset::AssetCallbackFlag;
261 use crate::testing::account_id::{
262 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
263 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
264 };
265
266 #[test]
267 fn asset_vault_key_word_roundtrip() -> anyhow::Result<()> {
268 let fungible_faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?;
269 let nonfungible_faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET)?;
270
271 for callback_flag in [AssetCallbackFlag::Disabled, AssetCallbackFlag::Enabled] {
272 let key = AssetVaultKey::new(AssetId::default(), fungible_faucet, callback_flag)?;
274
275 let roundtripped = AssetVaultKey::try_from(key.to_word())?;
276 assert_eq!(key, roundtripped);
277 assert_eq!(key, AssetVaultKey::read_from_bytes(&key.to_bytes())?);
278
279 let key = AssetVaultKey::new(
281 AssetId::new(Felt::from(42u32), Felt::from(99u32)),
282 nonfungible_faucet,
283 callback_flag,
284 )?;
285
286 let roundtripped = AssetVaultKey::try_from(key.to_word())?;
287 assert_eq!(key, roundtripped);
288 assert_eq!(key, AssetVaultKey::read_from_bytes(&key.to_bytes())?);
289 }
290
291 Ok(())
292 }
293}