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