miden_objects/account/component/template/
mod.rs1use alloc::{
2 collections::{BTreeMap, BTreeSet},
3 string::{String, ToString},
4 vec::Vec,
5};
6use core::str::FromStr;
7
8use assembly::Library;
9use semver::Version;
10use vm_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
11use vm_processor::DeserializationError;
12
13use super::AccountType;
14use crate::errors::AccountComponentTemplateError;
15
16mod storage;
17pub use storage::*;
18
19#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct AccountComponentTemplate {
33 metadata: AccountComponentMetadata,
36 library: Library,
39}
40
41impl AccountComponentTemplate {
42 pub fn new(metadata: AccountComponentMetadata, library: Library) -> Self {
50 Self { metadata, library }
51 }
52
53 pub fn metadata(&self) -> &AccountComponentMetadata {
55 &self.metadata
56 }
57
58 pub fn library(&self) -> &Library {
60 &self.library
61 }
62}
63
64impl Serializable for AccountComponentTemplate {
65 fn write_into<W: vm_core::utils::ByteWriter>(&self, target: &mut W) {
66 target.write(&self.metadata);
67 target.write(&self.library);
68 }
69}
70
71impl Deserializable for AccountComponentTemplate {
72 fn read_from<R: vm_core::utils::ByteReader>(
73 source: &mut R,
74 ) -> Result<Self, vm_processor::DeserializationError> {
75 let metadata: AccountComponentMetadata = source.read()?;
77 let library = Library::read_from(source)?;
78
79 Ok(AccountComponentTemplate::new(metadata, library))
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
150#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
151#[cfg_attr(feature = "std", serde(rename_all = "kebab-case"))]
152pub struct AccountComponentMetadata {
153 name: String,
155
156 description: String,
158
159 version: Version,
162
163 supported_types: BTreeSet<AccountType>,
165
166 storage: Vec<StorageEntry>,
169}
170
171impl AccountComponentMetadata {
172 pub fn new(
180 name: String,
181 description: String,
182 version: Version,
183 targets: BTreeSet<AccountType>,
184 storage: Vec<StorageEntry>,
185 ) -> Result<Self, AccountComponentTemplateError> {
186 let component = Self {
187 name,
188 description,
189 version,
190 supported_types: targets,
191 storage,
192 };
193 component.validate()?;
194 Ok(component)
195 }
196
197 pub fn get_placeholder_requirements(
207 &self,
208 ) -> BTreeMap<StorageValueName, PlaceholderTypeRequirement> {
209 let mut templates = BTreeMap::new();
210 for entry in self.storage_entries() {
211 for (name, requirement) in entry.template_requirements() {
212 templates.insert(name, requirement);
213 }
214 }
215
216 templates
217 }
218
219 pub fn name(&self) -> &str {
221 &self.name
222 }
223
224 pub fn description(&self) -> &str {
226 &self.description
227 }
228
229 pub fn version(&self) -> &Version {
231 &self.version
232 }
233
234 pub fn supported_types(&self) -> &BTreeSet<AccountType> {
236 &self.supported_types
237 }
238
239 pub fn storage_entries(&self) -> &Vec<StorageEntry> {
241 &self.storage
242 }
243
244 fn validate(&self) -> Result<(), AccountComponentTemplateError> {
253 let mut all_slots: Vec<u8> =
254 self.storage.iter().flat_map(|entry| entry.slot_indices()).collect();
255
256 all_slots.sort_unstable();
258 if let Some(&first_slot) = all_slots.first() {
259 if first_slot != 0 {
260 return Err(AccountComponentTemplateError::StorageSlotsDoNotStartAtZero(
261 first_slot,
262 ));
263 }
264 }
265
266 for slots in all_slots.windows(2) {
267 if slots[1] == slots[0] {
268 return Err(AccountComponentTemplateError::DuplicateSlot(slots[0]));
269 }
270
271 if slots[1] != slots[0] + 1 {
272 return Err(AccountComponentTemplateError::NonContiguousSlots(slots[0], slots[1]));
273 }
274 }
275
276 let mut seen_names = BTreeSet::new();
278 for entry in self.storage_entries() {
279 entry.validate()?;
280 if let Some(name) = entry.name() {
281 let name_existed = !seen_names.insert(name.as_str());
282 if name_existed {
283 return Err(AccountComponentTemplateError::DuplicateEntryNames(name.clone()));
284 }
285 }
286 }
287
288 let mut seen_placeholder_names = BTreeSet::new();
290 for entry in self.storage_entries() {
291 for (name, _) in entry.template_requirements() {
292 if !seen_placeholder_names.insert(name.clone()) {
293 return Err(AccountComponentTemplateError::DuplicatePlaceholderName(name));
294 }
295 }
296 }
297
298 Ok(())
299 }
300}
301
302impl Serializable for AccountComponentMetadata {
306 fn write_into<W: ByteWriter>(&self, target: &mut W) {
307 self.name.write_into(target);
308 self.description.write_into(target);
309 self.version.to_string().write_into(target);
310 self.supported_types.write_into(target);
311 self.storage.write_into(target);
312 }
313}
314
315impl Deserializable for AccountComponentMetadata {
316 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
317 Ok(Self {
318 name: String::read_from(source)?,
319 description: String::read_from(source)?,
320 version: semver::Version::from_str(&String::read_from(source)?).map_err(
321 |err: semver::Error| DeserializationError::InvalidValue(err.to_string()),
322 )?,
323 supported_types: BTreeSet::<AccountType>::read_from(source)?,
324 storage: Vec::<StorageEntry>::read_from(source)?,
325 })
326 }
327}
328
329#[cfg(test)]
333mod tests {
334 use std::{collections::BTreeSet, string::ToString};
335
336 use assembly::Assembler;
337 use assert_matches::assert_matches;
338 use semver::Version;
339 use vm_core::{
340 Felt, FieldElement,
341 utils::{Deserializable, Serializable},
342 };
343
344 use super::FeltRepresentation;
345 use crate::{
346 AccountError,
347 account::{
348 AccountComponent, StorageValueName,
349 component::{
350 FieldIdentifier,
351 template::{
352 AccountComponentMetadata, AccountComponentTemplate, InitStorageData,
353 storage::StorageEntry,
354 },
355 },
356 },
357 errors::AccountComponentTemplateError,
358 testing::account_code::CODE,
359 };
360
361 fn default_felt_array() -> [FeltRepresentation; 4] {
362 [
363 FeltRepresentation::from(Felt::ZERO),
364 FeltRepresentation::from(Felt::ZERO),
365 FeltRepresentation::from(Felt::ZERO),
366 FeltRepresentation::from(Felt::ZERO),
367 ]
368 }
369
370 #[test]
371 fn contiguous_value_slots() {
372 let storage = vec![
373 StorageEntry::new_value(0, default_felt_array()),
374 StorageEntry::new_multislot(
375 FieldIdentifier::with_name(StorageValueName::new("slot1").unwrap()),
376 1..3,
377 vec![default_felt_array(), default_felt_array()],
378 ),
379 ];
380
381 let original_config = AccountComponentMetadata {
382 name: "test".into(),
383 description: "desc".into(),
384 version: Version::parse("0.1.0").unwrap(),
385 supported_types: BTreeSet::new(),
386 storage,
387 };
388
389 let serialized = original_config.as_toml().unwrap();
390 let deserialized = AccountComponentMetadata::from_toml(&serialized).unwrap();
391 assert_eq!(deserialized, original_config);
392 }
393
394 #[test]
395 fn new_non_contiguous_value_slots() {
396 let storage = vec![
397 StorageEntry::new_value(0, default_felt_array()),
398 StorageEntry::new_value(2, default_felt_array()),
399 ];
400
401 let result = AccountComponentMetadata::new(
402 "test".into(),
403 "desc".into(),
404 Version::parse("0.1.0").unwrap(),
405 BTreeSet::new(),
406 storage,
407 );
408 assert_matches!(result, Err(AccountComponentTemplateError::NonContiguousSlots(0, 2)));
409 }
410
411 #[test]
412 fn binary_serde_roundtrip() {
413 let storage = vec![
414 StorageEntry::new_multislot(
415 FieldIdentifier::with_name(StorageValueName::new("slot1").unwrap()),
416 1..3,
417 vec![default_felt_array(), default_felt_array()],
418 ),
419 StorageEntry::new_value(0, default_felt_array()),
420 ];
421
422 let component_metadata = AccountComponentMetadata {
423 name: "test".into(),
424 description: "desc".into(),
425 version: Version::parse("0.1.0").unwrap(),
426 supported_types: BTreeSet::new(),
427 storage,
428 };
429
430 let library = Assembler::default().assemble_library([CODE]).unwrap();
431 let template = AccountComponentTemplate::new(component_metadata, library);
432 let _ = AccountComponent::from_template(&template, &InitStorageData::default()).unwrap();
433
434 let serialized = template.to_bytes();
435 let deserialized = AccountComponentTemplate::read_from_bytes(&serialized).unwrap();
436
437 assert_eq!(deserialized, template);
438 }
439
440 #[test]
441 pub fn fail_on_duplicate_key() {
442 let toml_text = r#"
443 name = "Test Component"
444 description = "This is a test component"
445 version = "1.0.1"
446 supported-types = ["FungibleFaucet"]
447
448 [[storage]]
449 name = "map"
450 description = "A storage map entry"
451 slot = 0
452 values = [
453 { key = "0x1", value = ["0x3", "0x1", "0x2", "0x3"] },
454 { key = "0x1", value = ["0x1", "0x2", "0x3", "0x10"] }
455 ]
456 "#;
457
458 let result = AccountComponentMetadata::from_toml(toml_text);
459 assert_matches!(result, Err(AccountComponentTemplateError::StorageMapHasDuplicateKeys(_)));
460 }
461
462 #[test]
463 pub fn fail_on_duplicate_placeholder_name() {
464 let toml_text = r#"
465 name = "Test Component"
466 description = "tests for two duplicate placeholders"
467 version = "1.0.1"
468 supported-types = ["FungibleFaucet"]
469
470 [[storage]]
471 name = "map"
472 slot = 0
473 values = [
474 { key = "0x1", value = [{type = "felt", name = "test"}, "0x1", "0x2", "0x3"] },
475 { key = "0x2", value = ["0x1", "0x2", "0x3", {type = "token_symbol", name = "test"}] }
476 ]
477 "#;
478
479 let result = AccountComponentMetadata::from_toml(toml_text).unwrap_err();
480 assert_matches::assert_matches!(
481 result,
482 AccountComponentTemplateError::DuplicatePlaceholderName(_)
483 );
484 }
485
486 #[test]
487 pub fn fail_duplicate_key_instance() {
488 let toml_text = r#"
489 name = "Test Component"
490 description = "This is a test component"
491 version = "1.0.1"
492 supported-types = ["FungibleFaucet"]
493
494 [[storage]]
495 name = "map"
496 description = "A storage map entry"
497 slot = 0
498 values = [
499 { key = ["0", "0", "0", "1"], value = ["0x9", "0x12", "0x31", "0x18"] },
500 { key = { name="duplicate_key" }, value = ["0x1", "0x2", "0x3", "0x4"] }
501 ]
502 "#;
503
504 let metadata = AccountComponentMetadata::from_toml(toml_text).unwrap();
505 let library = Assembler::default().assemble_library([CODE]).unwrap();
506 let template = AccountComponentTemplate::new(metadata, library);
507
508 let init_storage_data = InitStorageData::new([(
511 StorageValueName::new("map.duplicate_key").unwrap(),
512 "0x0000000000000000000000000000000000000000000000000100000000000000".to_string(),
513 )]);
514 let account_component = AccountComponent::from_template(&template, &init_storage_data);
515 assert_matches!(
516 account_component,
517 Err(AccountError::AccountComponentTemplateInstantiationError(
518 AccountComponentTemplateError::StorageMapHasDuplicateKeys(_)
519 ))
520 );
521
522 let valid_init_storage_data = InitStorageData::new([(
524 StorageValueName::new("map.duplicate_key").unwrap(),
525 "0x30".to_string(),
526 )]);
527 AccountComponent::from_template(&template, &valid_init_storage_data).unwrap();
528 }
529}