miden_protocol/account/patch/
vault.rs1use alloc::collections::BTreeMap;
2use alloc::string::ToString;
3use alloc::vec::Vec;
4
5use crate::asset::{Asset, AssetId};
6use crate::errors::AssetError;
7use crate::utils::serde::{
8 ByteReader,
9 ByteWriter,
10 Deserializable,
11 DeserializationError,
12 Serializable,
13};
14use crate::{Felt, Word};
15
16#[derive(Debug, Clone, Default, PartialEq, Eq)]
21pub struct AccountVaultPatch {
22 entries: BTreeMap<AssetId, Word>,
23}
24
25impl AccountVaultPatch {
26 const DOMAIN: Felt = Felt::new_unchecked(3);
28
29 pub fn new(entries: BTreeMap<AssetId, Word>) -> Result<Self, AssetError> {
36 for (key, value) in entries.iter() {
37 if !value.is_empty() {
40 Asset::from_id_and_value(*key, *value)?;
41 }
42 }
43
44 Ok(Self { entries })
45 }
46
47 pub fn insert_asset(&mut self, asset: Asset) {
49 self.entries.insert(asset.id(), asset.to_value_word());
50 }
51
52 pub fn remove_asset(&mut self, asset_id: AssetId) {
54 self.entries.insert(asset_id, Word::empty());
55 }
56
57 pub fn num_assets(&self) -> usize {
59 self.entries.len()
60 }
61
62 pub fn as_map(&self) -> &BTreeMap<AssetId, Word> {
64 &self.entries
65 }
66
67 pub fn into_map(self) -> BTreeMap<AssetId, Word> {
69 self.entries
70 }
71
72 pub fn iter(&self) -> impl Iterator<Item = (&AssetId, &Word)> {
74 self.entries.iter()
75 }
76
77 pub fn is_empty(&self) -> bool {
79 self.entries.is_empty()
80 }
81
82 pub fn merge(&mut self, other: Self) {
85 self.entries.extend(other.entries);
86 }
87
88 pub(super) fn append_patch_elements(&self, elements: &mut Vec<Felt>) {
91 for (asset_id, asset_value_or_empty_word) in self.entries.iter() {
92 elements.extend_from_slice(asset_id.to_word().as_elements());
93 elements.extend_from_slice(asset_value_or_empty_word.as_elements());
94 }
95
96 let num_changed_assets = self.entries.len();
97 if num_changed_assets != 0 {
98 let num_changed_assets = Felt::try_from(num_changed_assets as u64)
99 .expect("number of assets should not exceed max representable felt");
100
101 elements.extend_from_slice(&[Self::DOMAIN, num_changed_assets, Felt::ZERO, Felt::ZERO]);
102 elements.extend_from_slice(Word::empty().as_elements());
103 }
104 }
105
106 pub fn removed_asset_ids(&self) -> impl Iterator<Item = &AssetId> {
109 self.entries
110 .iter()
111 .filter(|(_key, value)| value.is_empty())
112 .map(|(key, _value)| key)
113 }
114
115 pub fn updated_assets(&self) -> impl Iterator<Item = Asset> {
118 self.entries
119 .iter()
120 .filter(|(_key, value)| !value.is_empty())
121 .map(|(key, value)| {
122 Asset::from_id_and_value(*key, *value).expect("patch should track valid assets")
123 })
124 }
125}
126
127impl Serializable for AccountVaultPatch {
128 fn write_into<W: ByteWriter>(&self, target: &mut W) {
129 target.write_usize(self.removed_asset_ids().count());
130 target.write_many(self.removed_asset_ids());
131
132 target.write_usize(self.updated_assets().count());
133 target.write_many(self.updated_assets());
134 }
135
136 fn get_size_hint(&self) -> usize {
137 let removed_size: usize =
138 self.removed_asset_ids().map(|asset_id| asset_id.get_size_hint()).sum();
139 let updated_size: usize = self.updated_assets().map(|asset| asset.get_size_hint()).sum();
140
141 2 * 0usize.get_size_hint() + removed_size + updated_size
142 }
143}
144
145impl Deserializable for AccountVaultPatch {
146 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
147 let num_removed_assets = source.read_usize()?;
148 let mut entries: BTreeMap<AssetId, Word> = source
149 .read_many_iter::<AssetId>(num_removed_assets)?
150 .map(|result| result.map(|id| (id, Word::empty())))
151 .collect::<Result<_, _>>()?;
152
153 let num_added_assets = source.read_usize()?;
154 for result in source.read_many_iter::<Asset>(num_added_assets)? {
155 let asset = result?;
156 entries.insert(asset.id(), asset.to_value_word());
157 }
158
159 Self::new(entries).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use crate::asset::{FungibleAsset, NonFungibleAsset};
167 use crate::testing::account_id::ACCOUNT_ID_PRIVATE_SENDER;
168
169 #[test]
170 fn account_vault_patch_serde() -> anyhow::Result<()> {
171 let empty_patch = AccountVaultPatch::default();
172 let serialized = empty_patch.to_bytes();
173 let deserialized = AccountVaultPatch::read_from_bytes(&serialized)?;
174 assert_eq!(empty_patch, deserialized);
175 assert_eq!(empty_patch.get_size_hint(), serialized.len());
176
177 let asset_0: Asset = FungibleAsset::mock(100);
178 let asset_1: Asset =
179 FungibleAsset::new(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, 500_000)?.into();
180 let asset_2: Asset = NonFungibleAsset::mock(&[10]);
181 let asset_3: Asset = NonFungibleAsset::mock(&[20]);
182 let patch = AccountVaultPatch::from_iters([asset_0, asset_1, asset_2], [asset_3]);
183
184 let serialized = patch.to_bytes();
185 let deserialized = AccountVaultPatch::read_from_bytes(&serialized)?;
186 assert_eq!(deserialized, patch);
187 assert_eq!(patch.get_size_hint(), serialized.len());
188
189 Ok(())
190 }
191}