1use alloc::{
2 collections::{btree_map::Entry, 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)]
151#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
152pub struct AccountComponentMetadata {
153 name: String,
155
156 description: String,
158
159 version: Version,
162
163 targets: 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 targets,
191 storage,
192 };
193 component.validate()?;
194 Ok(component)
195 }
196
197 pub fn get_unique_storage_placeholders(&self) -> BTreeMap<StoragePlaceholder, PlaceholderType> {
207 let mut placeholder_map = BTreeMap::new();
208 for storage_entry in &self.storage {
209 for (placeholder, placeholder_type) in storage_entry.all_placeholders_iter() {
210 placeholder_map.insert(placeholder.clone(), placeholder_type);
213 }
214 }
215 placeholder_map
216 }
217
218 pub fn name(&self) -> &str {
220 &self.name
221 }
222
223 pub fn description(&self) -> &str {
225 &self.description
226 }
227
228 pub fn version(&self) -> &Version {
230 &self.version
231 }
232
233 pub fn targets(&self) -> &BTreeSet<AccountType> {
235 &self.targets
236 }
237
238 pub fn storage_entries(&self) -> &Vec<StorageEntry> {
240 &self.storage
241 }
242
243 fn validate(&self) -> Result<(), AccountComponentTemplateError> {
252 let mut all_slots: Vec<u8> = self
253 .storage
254 .iter()
255 .flat_map(|entry| entry.slot_indices().iter().copied())
256 .collect();
257
258 all_slots.sort_unstable();
260 if let Some(&first_slot) = all_slots.first() {
261 if first_slot != 0 {
262 return Err(AccountComponentTemplateError::StorageSlotsDoNotStartAtZero(
263 first_slot,
264 ));
265 }
266 }
267
268 for slots in all_slots.windows(2) {
269 if slots[1] == slots[0] {
270 return Err(AccountComponentTemplateError::DuplicateSlot(slots[0]));
271 }
272
273 if slots[1] != slots[0] + 1 {
274 return Err(AccountComponentTemplateError::NonContiguousSlots(slots[0], slots[1]));
275 }
276 }
277
278 let mut placeholders = BTreeMap::new();
280 for storage_entry in &self.storage {
281 for (placeholder, placeholder_type) in storage_entry.all_placeholders_iter() {
282 match placeholders.entry(placeholder.clone()) {
283 Entry::Occupied(entry) => {
284 if *entry.get() != placeholder_type {
286 return Err(
287 AccountComponentTemplateError::StoragePlaceholderTypeMismatch(
288 placeholder.clone(),
289 *entry.get(),
290 placeholder_type,
291 ),
292 );
293 }
294 },
295 Entry::Vacant(slot) => {
296 slot.insert(placeholder_type);
297 },
298 }
299 }
300 }
301
302 for entry in self.storage_entries() {
303 entry.validate()?;
304 }
305
306 Ok(())
307 }
308}
309
310impl Serializable for AccountComponentMetadata {
314 fn write_into<W: ByteWriter>(&self, target: &mut W) {
315 self.name.write_into(target);
316 self.description.write_into(target);
317 self.version.to_string().write_into(target);
318 self.targets.write_into(target);
319 self.storage.write_into(target);
320 }
321}
322
323impl Deserializable for AccountComponentMetadata {
324 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
325 Ok(Self {
326 name: String::read_from(source)?,
327 description: String::read_from(source)?,
328 version: semver::Version::from_str(&String::read_from(source)?).map_err(
329 |err: semver::Error| DeserializationError::InvalidValue(err.to_string()),
330 )?,
331 targets: BTreeSet::<AccountType>::read_from(source)?,
332 storage: Vec::<StorageEntry>::read_from(source)?,
333 })
334 }
335}
336
337#[cfg(test)]
341mod tests {
342 use assembly::Assembler;
343 use assert_matches::assert_matches;
344 use storage::WordRepresentation;
345 use vm_core::{Felt, FieldElement};
346
347 use super::*;
348 use crate::{account::AccountComponent, testing::account_code::CODE, AccountError};
349
350 #[test]
351 fn test_contiguous_value_slots() {
352 let storage = vec![
353 StorageEntry::Value {
354 name: "slot0".into(),
355 description: None,
356 slot: 0,
357 value: WordRepresentation::Value(Default::default()),
358 },
359 StorageEntry::MultiSlot {
360 name: "slot1".into(),
361 description: None,
362 slots: vec![1, 2],
363 values: vec![
364 WordRepresentation::Array(Default::default()),
365 WordRepresentation::Value(Default::default()),
366 ],
367 },
368 ];
369
370 let original_config = AccountComponentMetadata::new(
371 "test".into(),
372 "desc".into(),
373 Version::parse("0.1.0").unwrap(),
374 BTreeSet::new(),
375 storage,
376 )
377 .unwrap();
378
379 let serialized = original_config.as_toml().unwrap();
380
381 let deserialized = AccountComponentMetadata::from_toml(&serialized).unwrap();
382 assert_eq!(deserialized, original_config)
383 }
384
385 #[test]
386 fn test_new_non_contiguous_value_slots() {
387 let storage = vec![
388 StorageEntry::Value {
389 name: "slot0".into(),
390 description: None,
391 slot: 0,
392 value: Default::default(),
393 },
394 StorageEntry::Value {
395 name: "slot2".into(),
396 description: None,
397 slot: 2,
398 value: Default::default(),
399 },
400 ];
401
402 let result = AccountComponentMetadata::new(
403 "test".into(),
404 "desc".into(),
405 Version::parse("0.1.0").unwrap(),
406 BTreeSet::new(),
407 storage,
408 );
409 assert_matches!(result, Err(AccountComponentTemplateError::NonContiguousSlots(0, 2)));
410 }
411
412 #[test]
413 fn test_binary_serde_roundtrip() {
414 let storage = vec![
415 StorageEntry::MultiSlot {
416 name: "slot1".into(),
417 description: None,
418 slots: vec![1, 2],
419 values: vec![
420 WordRepresentation::Array(Default::default()),
421 WordRepresentation::Value(Default::default()),
422 ],
423 },
424 StorageEntry::Value {
425 name: "slot0".into(),
426 description: None,
427 slot: 0,
428 value: WordRepresentation::Value(Default::default()),
429 },
430 ];
431
432 let component_template = AccountComponentMetadata::new(
433 "test".into(),
434 "desc".into(),
435 Version::parse("0.1.0").unwrap(),
436 BTreeSet::new(),
437 storage,
438 )
439 .unwrap();
440
441 let library = Assembler::default().assemble_library([CODE]).unwrap();
442 let template = AccountComponentTemplate::new(component_template, library);
443 _ = AccountComponent::from_template(&template, &InitStorageData::default()).unwrap();
444
445 let serialized = template.to_bytes();
446 let deserialized = AccountComponentTemplate::read_from_bytes(&serialized).unwrap();
447
448 assert_eq!(deserialized, template)
449 }
450
451 #[test]
452 pub fn fail_duplicate_key() {
453 let toml_text = r#"
454 name = "Test Component"
455 description = "This is a test component"
456 version = "1.0.1"
457 targets = ["FungibleFaucet"]
458
459 [[storage]]
460 name = "map"
461 description = "A storage map entry"
462 slot = 0
463 values = [
464 { key = "0x1", value = ["{{value.test}}", "0x1", "0x2", "0x3"] },
465 { key = "0x1", value = ["0x1", "0x2", "0x3", "{{value.test}}"] },
466 ]
467 "#;
468
469 let result = AccountComponentMetadata::from_toml(toml_text);
470 assert_matches!(result, Err(AccountComponentTemplateError::StorageMapHasDuplicateKeys(_)));
471 }
472
473 #[test]
474 pub fn fail_duplicate_key_instance() {
475 let toml_text = r#"
476 name = "Test Component"
477 description = "This is a test component"
478 version = "1.0.1"
479 targets = ["FungibleFaucet"]
480
481 [[storage]]
482 name = "map"
483 description = "A storage map entry"
484 slot = 0
485 values = [
486 { key = ["0","0","0","1"], value = ["{{value.test}}", "0x1", "0x2", "0x3"] },
487 { key = "{{word.test}}", value = ["0x1", "0x2", "0x3", "{{value.test}}"] },
488 ]
489 "#;
490
491 let metadata = AccountComponentMetadata::from_toml(toml_text).unwrap();
492 let library = Assembler::default().assemble_library([CODE]).unwrap();
493 let template = AccountComponentTemplate::new(metadata, library);
494
495 let init_storage_data = InitStorageData::new([
496 (
497 StoragePlaceholder::new("word.test").unwrap(),
498 StorageValue::Word([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ONE]),
499 ),
500 (StoragePlaceholder::new("value.test").unwrap(), StorageValue::Felt(Felt::ONE)),
501 ]);
502 let account_component = AccountComponent::from_template(&template, &init_storage_data);
503 assert_matches!(
504 account_component,
505 Err(AccountError::AccountComponentTemplateInstantiationError(
506 AccountComponentTemplateError::StorageMapHasDuplicateKeys(_)
507 ))
508 );
509
510 let valid_init_storage_data = InitStorageData::new([
511 (
512 StoragePlaceholder::new("word.test").unwrap(),
513 StorageValue::Word([Felt::new(30), Felt::new(20), Felt::new(10), Felt::ZERO]),
514 ),
515 (StoragePlaceholder::new("value.test").unwrap(), StorageValue::Felt(Felt::ONE)),
516 ]);
517 AccountComponent::from_template(&template, &valid_init_storage_data).unwrap();
518 }
519}