Skip to main content

miden_protocol/note/
assets.rs

1use alloc::vec::Vec;
2
3use miden_crypto::SequentialCommit;
4
5use crate::asset::{Asset, FungibleAsset, NonFungibleAsset};
6use crate::errors::NoteError;
7use crate::utils::serde::{
8    ByteReader,
9    ByteWriter,
10    Deserializable,
11    DeserializationError,
12    Serializable,
13};
14use crate::{Felt, Hasher, MAX_ASSETS_PER_NOTE, WORD_SIZE, Word};
15
16// NOTE ASSETS
17// ================================================================================================
18
19/// An asset container for a note.
20///
21/// A note can contain between 0 and 255 assets. No duplicates are allowed, but the order of assets
22/// is unspecified.
23///
24/// All the assets in a note can be reduced to a single commitment which is computed by
25/// sequentially hashing the assets. Note that the same list of assets can result in two different
26/// commitments if the asset ordering is different.
27#[derive(Debug, Default, Clone)]
28pub struct NoteAssets {
29    assets: Vec<Asset>,
30    commitment: Word,
31}
32
33impl NoteAssets {
34    // CONSTANTS
35    // --------------------------------------------------------------------------------------------
36
37    /// The maximum number of assets which can be carried by a single note.
38    pub const MAX_NUM_ASSETS: usize = MAX_ASSETS_PER_NOTE;
39
40    // CONSTRUCTOR
41    // --------------------------------------------------------------------------------------------
42
43    /// Returns new [NoteAssets] constructed from the provided list of assets.
44    ///
45    /// # Errors
46    /// Returns an error if:
47    /// - The list contains more than 256 assets.
48    /// - There are duplicate assets in the list.
49    pub fn new(assets: Vec<Asset>) -> Result<Self, NoteError> {
50        if assets.len() > Self::MAX_NUM_ASSETS {
51            return Err(NoteError::TooManyAssets(assets.len()));
52        }
53
54        // make sure all provided assets are unique
55        for (i, asset) in assets.iter().enumerate().skip(1) {
56            // for all assets except the first one, check if the asset is the same as any other
57            // asset in the list, and if so return an error
58            if assets[..i].iter().any(|a| a.is_same(asset)) {
59                return Err(match asset {
60                    Asset::Fungible(asset) => NoteError::DuplicateFungibleAsset(asset.faucet_id()),
61                    Asset::NonFungible(asset) => NoteError::DuplicateNonFungibleAsset(*asset),
62                });
63            }
64        }
65
66        let commitment = to_commitment(&assets);
67
68        Ok(Self { assets, commitment })
69    }
70
71    // PUBLIC ACCESSORS
72    // --------------------------------------------------------------------------------------------
73
74    /// Returns a commitment to the note's assets.
75    pub fn commitment(&self) -> Word {
76        self.commitment
77    }
78
79    /// Returns the number of assets.
80    pub fn num_assets(&self) -> usize {
81        self.assets.len()
82    }
83
84    /// Returns true if the number of assets is 0.
85    pub fn is_empty(&self) -> bool {
86        self.assets.is_empty()
87    }
88
89    /// Returns an iterator over all assets.
90    pub fn iter(&self) -> core::slice::Iter<'_, Asset> {
91        self.assets.iter()
92    }
93
94    /// Returns all assets represented as a vector of field elements.
95    pub fn to_elements(&self) -> Vec<Felt> {
96        <Self as SequentialCommit>::to_elements(self)
97    }
98
99    /// Returns an iterator over all [`FungibleAsset`].
100    pub fn iter_fungible(&self) -> impl Iterator<Item = FungibleAsset> {
101        self.assets.iter().filter_map(|asset| match asset {
102            Asset::Fungible(fungible_asset) => Some(*fungible_asset),
103            Asset::NonFungible(_) => None,
104        })
105    }
106
107    /// Returns iterator over all [`NonFungibleAsset`].
108    pub fn iter_non_fungible(&self) -> impl Iterator<Item = NonFungibleAsset> {
109        self.assets.iter().filter_map(|asset| match asset {
110            Asset::Fungible(_) => None,
111            Asset::NonFungible(non_fungible_asset) => Some(*non_fungible_asset),
112        })
113    }
114
115    // STATE MUTATORS
116    // --------------------------------------------------------------------------------------------
117
118    /// Adds the provided asset to this list of note assets.
119    ///
120    /// # Errors
121    /// Returns an error if:
122    /// - The same non-fungible asset is already in the list.
123    /// - A fungible asset issued by the same faucet exists in the list and adding both assets
124    ///   together results in an invalid asset.
125    /// - Adding the asset to the list will push the list beyond the [Self::MAX_NUM_ASSETS] limit.
126    pub fn add_asset(&mut self, asset: Asset) -> Result<(), NoteError> {
127        // check if the asset issued by the faucet as the provided asset already exists in the
128        // list of assets
129        if let Some(own_asset) = self.assets.iter_mut().find(|a| a.is_same(&asset)) {
130            match own_asset {
131                Asset::Fungible(f_own_asset) => {
132                    // if a fungible asset issued by the same faucet is found, try to add the
133                    // the provided asset to it
134                    let new_asset = f_own_asset
135                        .add(asset.unwrap_fungible())
136                        .map_err(NoteError::AddFungibleAssetBalanceError)?;
137                    *own_asset = Asset::Fungible(new_asset);
138                },
139                Asset::NonFungible(nf_asset) => {
140                    return Err(NoteError::DuplicateNonFungibleAsset(*nf_asset));
141                },
142            }
143        } else {
144            // if the asset is not in the list, add it to the list
145            self.assets.push(asset);
146            if self.assets.len() > Self::MAX_NUM_ASSETS {
147                return Err(NoteError::TooManyAssets(self.assets.len()));
148            }
149        }
150
151        // Recompute the commitment.
152        self.commitment = self.to_commitment();
153
154        Ok(())
155    }
156}
157
158impl PartialEq for NoteAssets {
159    fn eq(&self, other: &Self) -> bool {
160        self.assets == other.assets
161    }
162}
163
164impl Eq for NoteAssets {}
165
166impl SequentialCommit for NoteAssets {
167    type Commitment = Word;
168
169    /// Returns all assets represented as a vector of field elements.
170    fn to_elements(&self) -> Vec<Felt> {
171        to_elements(&self.assets)
172    }
173
174    /// Computes the commitment to the assets.
175    fn to_commitment(&self) -> Self::Commitment {
176        to_commitment(&self.assets)
177    }
178}
179
180fn to_elements(assets: &[Asset]) -> Vec<Felt> {
181    let mut elements = Vec::with_capacity(assets.len() * 2 * WORD_SIZE);
182    elements.extend(assets.iter().flat_map(Asset::as_elements));
183    elements
184}
185
186fn to_commitment(assets: &[Asset]) -> Word {
187    Hasher::hash_elements(&to_elements(assets))
188}
189
190// SERIALIZATION
191// ================================================================================================
192
193impl Serializable for NoteAssets {
194    fn write_into<W: ByteWriter>(&self, target: &mut W) {
195        const _: () = assert!(NoteAssets::MAX_NUM_ASSETS <= u8::MAX as usize);
196        debug_assert!(self.assets.len() <= NoteAssets::MAX_NUM_ASSETS);
197        target.write_u8(self.assets.len().try_into().expect("Asset number must fit into `u8`"));
198        target.write_many(&self.assets);
199    }
200
201    fn get_size_hint(&self) -> usize {
202        // Size of the serialized asset count prefix.
203        let u8_size = 0u8.get_size_hint();
204
205        let assets_size: usize = self.assets.iter().map(|asset| asset.get_size_hint()).sum();
206
207        u8_size + assets_size
208    }
209}
210
211impl Deserializable for NoteAssets {
212    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
213        let count = source.read_u8()?;
214        let assets = source.read_many_iter::<Asset>(count.into())?.collect::<Result<_, _>>()?;
215        Self::new(assets).map_err(|e| DeserializationError::InvalidValue(format!("{e:?}")))
216    }
217}
218
219// TESTS
220// ================================================================================================
221
222#[cfg(test)]
223mod tests {
224    use super::NoteAssets;
225    use crate::Word;
226    use crate::account::AccountId;
227    use crate::asset::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
228    use crate::testing::account_id::{
229        ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
230        ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
231        ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
232    };
233
234    #[test]
235    fn add_asset() {
236        let faucet_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap();
237
238        let asset1 = Asset::Fungible(FungibleAsset::new(faucet_id, 100).unwrap());
239        let asset2 = Asset::Fungible(FungibleAsset::new(faucet_id, 50).unwrap());
240
241        // create empty assets
242        let mut assets = NoteAssets::default();
243
244        assert_eq!(assets.commitment, Word::empty());
245
246        // add asset1
247        assert!(assets.add_asset(asset1).is_ok());
248        assert_eq!(assets.assets, vec![asset1]);
249        assert!(!assets.commitment.is_empty());
250
251        // add asset2
252        assert!(assets.add_asset(asset2).is_ok());
253        let expected_asset = Asset::Fungible(FungibleAsset::new(faucet_id, 150).unwrap());
254        assert_eq!(assets.assets, vec![expected_asset]);
255        assert!(!assets.commitment.is_empty());
256    }
257    #[test]
258    fn iter_fungible_asset() {
259        let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap();
260        let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
261        let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap();
262        let details = NonFungibleAssetDetails::new(account_id, vec![1, 2, 3]).unwrap();
263
264        let asset1 = Asset::Fungible(FungibleAsset::new(faucet_id_1, 100).unwrap());
265        let asset2 = Asset::Fungible(FungibleAsset::new(faucet_id_2, 50).unwrap());
266        let non_fungible_asset = Asset::NonFungible(NonFungibleAsset::new(&details).unwrap());
267
268        // Create NoteAsset from assets
269        let assets = NoteAssets::new([asset1, asset2, non_fungible_asset].to_vec()).unwrap();
270
271        let mut fungible_assets = assets.iter_fungible();
272        assert_eq!(fungible_assets.next().unwrap(), asset1.unwrap_fungible());
273        assert_eq!(fungible_assets.next().unwrap(), asset2.unwrap_fungible());
274        assert_eq!(fungible_assets.next(), None);
275    }
276}