miden_objects/account/code/
mod.rs1use alloc::{collections::BTreeSet, sync::Arc, vec::Vec};
2
3use vm_core::{mast::MastForest, prettier::PrettyPrint};
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, PrintableProcedure};
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 pub fn printable_procedures(&self) -> impl Iterator<Item = PrintableProcedure> {
230 self.procedures()
231 .iter()
232 .filter_map(move |procedure_info| self.printable_procedure(procedure_info).ok())
233 }
234
235 fn printable_procedure(
243 &self,
244 proc_info: &AccountProcedureInfo,
245 ) -> Result<PrintableProcedure, AccountError> {
246 let node_id = self
247 .mast
248 .find_procedure_root(*proc_info.mast_root())
249 .expect("procedure root should be present in the mast forest");
250
251 Ok(PrintableProcedure::new(self.mast.clone(), *proc_info, node_id))
252 }
253}
254
255impl PartialEq for AccountCode {
259 fn eq(&self, other: &Self) -> bool {
260 self.mast == other.mast && self.procedures == other.procedures
262 }
263}
264
265impl Ord for AccountCode {
266 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
267 self.commitment.cmp(&other.commitment)
268 }
269}
270
271impl PartialOrd for AccountCode {
272 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
273 Some(self.cmp(other))
274 }
275}
276
277impl Eq for AccountCode {}
278
279impl Serializable for AccountCode {
283 fn write_into<W: ByteWriter>(&self, target: &mut W) {
284 self.mast.write_into(target);
285 target.write_u8((self.procedures.len() - 1) as u8);
288 target.write_many(self.procedures());
289 }
290
291 fn get_size_hint(&self) -> usize {
292 let mut mast_forest_target = Vec::new();
294 self.mast.write_into(&mut mast_forest_target);
295
296 let u8_size = 0u8.get_size_hint();
298 let mut size = u8_size + mast_forest_target.len();
299
300 for procedure in self.procedures() {
301 size += procedure.get_size_hint();
302 }
303
304 size
305 }
306}
307
308impl Deserializable for AccountCode {
309 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
310 let module = Arc::new(MastForest::read_from(source)?);
311 let num_procedures = (source.read_u8()? as usize) + 1;
312 let procedures = source.read_many::<AccountProcedureInfo>(num_procedures)?;
313
314 Ok(Self::from_parts(module, procedures))
315 }
316}
317
318impl PrettyPrint for AccountCode {
322 fn render(&self) -> vm_core::prettier::Document {
323 use vm_core::prettier::*;
324 let mut partial = Document::Empty;
325 let len_procedures = self.num_procedures();
326
327 for (index, printable_procedure) in self.printable_procedures().enumerate() {
328 partial += indent(
329 0,
330 indent(
331 4,
332 text(format!("proc.{}", printable_procedure.mast_root()))
333 + nl()
334 + text(format!(
335 "storage.{}.{}",
336 printable_procedure.storage_offset(),
337 printable_procedure.storage_size()
338 ))
339 + nl()
340 + printable_procedure.render(),
341 ) + nl()
342 + const_text("end"),
343 );
344 if index < len_procedures - 1 {
345 partial += nl();
346 }
347 }
348 partial
349 }
350}
351
352pub(crate) fn build_procedure_commitment(procedures: &[AccountProcedureInfo]) -> Digest {
357 let elements = procedures_as_elements(procedures);
358 Hasher::hash_elements(&elements)
359}
360
361pub(crate) fn procedures_as_elements(procedures: &[AccountProcedureInfo]) -> Vec<Felt> {
363 procedures.iter().flat_map(|procedure| <[Felt; 8]>::from(*procedure)).collect()
364}
365
366#[cfg(test)]
370mod tests {
371
372 use assembly::Assembler;
373 use assert_matches::assert_matches;
374 use vm_core::Word;
375
376 use super::{AccountCode, Deserializable, Serializable};
377 use crate::{
378 AccountError,
379 account::{AccountComponent, AccountType, StorageSlot, code::build_procedure_commitment},
380 };
381
382 #[test]
383 fn test_serde_account_code() {
384 let code = AccountCode::mock();
385 let serialized = code.to_bytes();
386 let deserialized = AccountCode::read_from_bytes(&serialized).unwrap();
387 assert_eq!(deserialized, code)
388 }
389
390 #[test]
391 fn test_account_code_procedure_root() {
392 let code = AccountCode::mock();
393 let procedure_root = build_procedure_commitment(code.procedures());
394 assert_eq!(procedure_root, code.commitment())
395 }
396
397 #[test]
398 fn test_account_code_procedure_offset_out_of_bounds() {
399 let code1 = "export.foo add end";
400 let library1 = Assembler::default().assemble_library([code1]).unwrap();
401 let code2 = "export.bar sub end";
402 let library2 = Assembler::default().assemble_library([code2]).unwrap();
403
404 let component1 =
405 AccountComponent::new(library1, vec![StorageSlot::Value(Word::default()); 250])
406 .unwrap()
407 .with_supports_all_types();
408 let mut component2 =
409 AccountComponent::new(library2, vec![StorageSlot::Value(Word::default()); 5])
410 .unwrap()
411 .with_supports_all_types();
412
413 AccountCode::from_components(
415 &[component1.clone(), component2.clone()],
416 AccountType::RegularAccountUpdatableCode,
417 )
418 .unwrap();
419
420 component2.storage_slots.push(StorageSlot::Value(Word::default()));
422
423 let err = AccountCode::from_components(
424 &[component1, component2],
425 AccountType::RegularAccountUpdatableCode,
426 )
427 .unwrap_err();
428
429 assert_matches!(err, AccountError::StorageOffsetPlusSizeOutOfBounds(256))
430 }
431}