miden_objects/account/code/
mod.rs1use alloc::{collections::BTreeSet, sync::Arc, vec::Vec};
2
3use vm_core::mast::MastForest;
4
5use super::{
6 AccountError, ByteReader, ByteWriter, Deserializable, DeserializationError, Digest, Felt,
7 Hasher, Serializable,
8};
9use crate::account::{AccountComponent, AccountType};
10
11pub mod procedure;
12use procedure::AccountProcedureInfo;
13
14#[derive(Debug, Clone)]
32pub struct AccountCode {
33 mast: Arc<MastForest>,
34 procedures: Vec<AccountProcedureInfo>,
35 commitment: Digest,
36}
37
38impl AccountCode {
39 pub const MAX_NUM_PROCEDURES: usize = 256;
41
42 #[cfg(any(feature = "testing", test))]
49 pub fn from_components(
50 components: &[AccountComponent],
51 account_type: AccountType,
52 ) -> Result<Self, AccountError> {
53 super::validate_components_support_account_type(components, account_type)?;
54 Self::from_components_unchecked(components, account_type)
55 }
56
57 pub(super) fn from_components_unchecked(
73 components: &[AccountComponent],
74 account_type: AccountType,
75 ) -> Result<Self, AccountError> {
76 let (merged_mast_forest, _) =
77 MastForest::merge(components.iter().map(|component| component.mast_forest()))
78 .map_err(AccountError::AccountComponentMastForestMergeError)?;
79
80 let mut procedures = Vec::new();
81 let mut proc_root_set = BTreeSet::new();
82
83 let mut component_storage_offset = if account_type.is_faucet() { 1 } else { 0 };
86
87 for component in components {
88 let component_storage_size = component.storage_size();
89
90 for module in component.library().module_infos() {
91 for proc_mast_root in module.procedure_digests() {
92 if !proc_root_set.insert(proc_mast_root) {
97 return Err(AccountError::AccountComponentDuplicateProcedureRoot(
98 proc_mast_root,
99 ));
100 }
101
102 let (storage_offset, storage_size) = if component_storage_size == 0 {
104 (0, 0)
105 } else {
106 (component_storage_offset, component_storage_size)
107 };
108
109 procedures.push(AccountProcedureInfo::new(
111 proc_mast_root,
112 storage_offset,
113 storage_size,
114 )?);
115 }
116 }
117
118 component_storage_offset = component_storage_offset.checked_add(component_storage_size)
119 .expect("account procedure info constructor should return an error if the addition overflows");
120 }
121
122 if procedures.is_empty() {
124 return Err(AccountError::AccountCodeNoProcedures);
125 } else if procedures.len() > Self::MAX_NUM_PROCEDURES {
126 return Err(AccountError::AccountCodeTooManyProcedures(procedures.len()));
127 }
128
129 Ok(Self {
130 commitment: build_procedure_commitment(&procedures),
131 procedures,
132 mast: Arc::new(merged_mast_forest),
133 })
134 }
135
136 pub fn from_bytes(bytes: &[u8]) -> Result<Self, AccountError> {
141 Self::read_from_bytes(bytes).map_err(AccountError::AccountCodeDeserializationError)
142 }
143
144 pub fn from_parts(mast: Arc<MastForest>, procedures: Vec<AccountProcedureInfo>) -> Self {
153 assert!(!procedures.is_empty(), "no account procedures");
154 assert!(procedures.len() <= Self::MAX_NUM_PROCEDURES, "too many account procedures");
155
156 Self {
157 commitment: build_procedure_commitment(&procedures),
158 procedures,
159 mast,
160 }
161 }
162
163 pub fn commitment(&self) -> Digest {
168 self.commitment
169 }
170
171 pub fn mast(&self) -> Arc<MastForest> {
173 self.mast.clone()
174 }
175
176 pub fn procedures(&self) -> &[AccountProcedureInfo] {
178 &self.procedures
179 }
180
181 pub fn procedure_roots(&self) -> impl Iterator<Item = Digest> + '_ {
183 self.procedures().iter().map(|procedure| *procedure.mast_root())
184 }
185
186 pub fn num_procedures(&self) -> usize {
188 self.procedures.len()
189 }
190
191 pub fn has_procedure(&self, mast_root: Digest) -> bool {
193 self.procedures.iter().any(|procedure| procedure.mast_root() == &mast_root)
194 }
195
196 pub fn get_procedure_by_index(&self, index: usize) -> &AccountProcedureInfo {
201 &self.procedures[index]
202 }
203
204 pub fn get_procedure_index_by_root(&self, root: Digest) -> Option<usize> {
207 self.procedures
208 .iter()
209 .map(|procedure| procedure.mast_root())
210 .position(|r| r == &root)
211 }
212
213 pub fn as_elements(&self) -> Vec<Felt> {
221 procedures_as_elements(self.procedures())
222 }
223}
224
225impl PartialEq for AccountCode {
229 fn eq(&self, other: &Self) -> bool {
230 self.mast == other.mast && self.procedures == other.procedures
232 }
233}
234
235impl Ord for AccountCode {
236 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
237 self.commitment.cmp(&other.commitment)
238 }
239}
240
241impl PartialOrd for AccountCode {
242 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
243 Some(self.cmp(other))
244 }
245}
246
247impl Eq for AccountCode {}
248
249impl Serializable for AccountCode {
253 fn write_into<W: ByteWriter>(&self, target: &mut W) {
254 self.mast.write_into(target);
255 target.write_u8((self.procedures.len() - 1) as u8);
258 target.write_many(self.procedures());
259 }
260
261 fn get_size_hint(&self) -> usize {
262 let mut mast_forest_target = Vec::new();
264 self.mast.write_into(&mut mast_forest_target);
265
266 let u8_size = 0u8.get_size_hint();
268 let mut size = u8_size + mast_forest_target.len();
269
270 for procedure in self.procedures() {
271 size += procedure.get_size_hint();
272 }
273
274 size
275 }
276}
277
278impl Deserializable for AccountCode {
279 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
280 let module = Arc::new(MastForest::read_from(source)?);
281 let num_procedures = (source.read_u8()? as usize) + 1;
282 let procedures = source.read_many::<AccountProcedureInfo>(num_procedures)?;
283
284 Ok(Self::from_parts(module, procedures))
285 }
286}
287
288fn procedures_as_elements(procedures: &[AccountProcedureInfo]) -> Vec<Felt> {
293 procedures
294 .iter()
295 .flat_map(|procedure| <[Felt; 8]>::from(procedure.clone()))
296 .collect()
297}
298
299fn build_procedure_commitment(procedures: &[AccountProcedureInfo]) -> Digest {
301 let elements = procedures_as_elements(procedures);
302 Hasher::hash_elements(&elements)
303}
304
305#[cfg(test)]
309mod tests {
310 use assembly::Assembler;
311 use assert_matches::assert_matches;
312 use vm_core::Word;
313
314 use super::{AccountCode, Deserializable, Serializable};
315 use crate::{
316 account::{code::build_procedure_commitment, AccountComponent, AccountType, StorageSlot},
317 AccountError,
318 };
319
320 #[test]
321 fn test_serde_account_code() {
322 let code = AccountCode::mock();
323 let serialized = code.to_bytes();
324 let deserialized = AccountCode::read_from_bytes(&serialized).unwrap();
325 assert_eq!(deserialized, code)
326 }
327
328 #[test]
329 fn test_account_code_procedure_commitment() {
330 let code = AccountCode::mock();
331 let procedure_commitment = build_procedure_commitment(code.procedures());
332 assert_eq!(procedure_commitment, code.commitment())
333 }
334
335 #[test]
336 fn test_account_code_procedure_offset_out_of_bounds() {
337 let code1 = "export.foo add end";
338 let library1 = Assembler::default().assemble_library([code1]).unwrap();
339 let code2 = "export.bar sub end";
340 let library2 = Assembler::default().assemble_library([code2]).unwrap();
341
342 let component1 =
343 AccountComponent::new(library1, vec![StorageSlot::Value(Word::default()); 250])
344 .unwrap()
345 .with_supports_all_types();
346 let mut component2 =
347 AccountComponent::new(library2, vec![StorageSlot::Value(Word::default()); 5])
348 .unwrap()
349 .with_supports_all_types();
350
351 AccountCode::from_components(
353 &[component1.clone(), component2.clone()],
354 AccountType::RegularAccountUpdatableCode,
355 )
356 .unwrap();
357
358 component2.storage_slots.push(StorageSlot::Value(Word::default()));
360
361 let err = AccountCode::from_components(
362 &[component1, component2],
363 AccountType::RegularAccountUpdatableCode,
364 )
365 .unwrap_err();
366
367 assert_matches!(err, AccountError::StorageOffsetPlusSizeOutOfBounds(256))
368 }
369}