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)]
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 && first_slot != 0
260 {
261 return Err(AccountComponentTemplateError::StorageSlotsDoNotStartAtZero(first_slot));
262 }
263
264 for slots in all_slots.windows(2) {
265 if slots[1] == slots[0] {
266 return Err(AccountComponentTemplateError::DuplicateSlot(slots[0]));
267 }
268
269 if slots[1] != slots[0] + 1 {
270 return Err(AccountComponentTemplateError::NonContiguousSlots(slots[0], slots[1]));
271 }
272 }
273
274 let mut seen_names = BTreeSet::new();
276 for entry in self.storage_entries() {
277 entry.validate()?;
278 if let Some(name) = entry.name() {
279 let name_existed = !seen_names.insert(name.as_str());
280 if name_existed {
281 return Err(AccountComponentTemplateError::DuplicateEntryNames(name.clone()));
282 }
283 }
284 }
285
286 let mut seen_placeholder_names = BTreeSet::new();
288 for entry in self.storage_entries() {
289 for (name, _) in entry.template_requirements() {
290 if !seen_placeholder_names.insert(name.clone()) {
291 return Err(AccountComponentTemplateError::DuplicatePlaceholderName(name));
292 }
293 }
294 }
295
296 Ok(())
297 }
298}
299
300impl Serializable for AccountComponentMetadata {
304 fn write_into<W: ByteWriter>(&self, target: &mut W) {
305 self.name.write_into(target);
306 self.description.write_into(target);
307 self.version.to_string().write_into(target);
308 self.supported_types.write_into(target);
309 self.storage.write_into(target);
310 }
311}
312
313impl Deserializable for AccountComponentMetadata {
314 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
315 Ok(Self {
316 name: String::read_from(source)?,
317 description: String::read_from(source)?,
318 version: semver::Version::from_str(&String::read_from(source)?).map_err(
319 |err: semver::Error| DeserializationError::InvalidValue(err.to_string()),
320 )?,
321 supported_types: BTreeSet::<AccountType>::read_from(source)?,
322 storage: Vec::<StorageEntry>::read_from(source)?,
323 })
324 }
325}
326
327#[cfg(test)]
331mod tests {
332 use std::collections::{BTreeMap, BTreeSet};
333 use std::string::ToString;
334
335 use assert_matches::assert_matches;
336 use miden_assembly::Assembler;
337 use miden_core::utils::{Deserializable, Serializable};
338 use miden_core::{Felt, FieldElement};
339 use semver::Version;
340
341 use super::FeltRepresentation;
342 use crate::AccountError;
343 use crate::account::component::FieldIdentifier;
344 use crate::account::component::template::storage::StorageEntry;
345 use crate::account::component::template::{
346 AccountComponentMetadata,
347 AccountComponentTemplate,
348 InitStorageData,
349 };
350 use crate::account::{AccountComponent, StorageValueName};
351 use crate::errors::AccountComponentTemplateError;
352 use crate::testing::account_code::CODE;
353
354 fn default_felt_array() -> [FeltRepresentation; 4] {
355 [
356 FeltRepresentation::from(Felt::ZERO),
357 FeltRepresentation::from(Felt::ZERO),
358 FeltRepresentation::from(Felt::ZERO),
359 FeltRepresentation::from(Felt::ZERO),
360 ]
361 }
362
363 #[test]
364 fn contiguous_value_slots() {
365 let storage = vec![
366 StorageEntry::new_value(0, default_felt_array()),
367 StorageEntry::new_multislot(
368 FieldIdentifier::with_name(StorageValueName::new("slot1").unwrap()),
369 1..3,
370 vec![default_felt_array(), default_felt_array()],
371 ),
372 ];
373
374 let original_config = AccountComponentMetadata {
375 name: "test".into(),
376 description: "desc".into(),
377 version: Version::parse("0.1.0").unwrap(),
378 supported_types: BTreeSet::new(),
379 storage,
380 };
381
382 let serialized = original_config.to_toml().unwrap();
383 let deserialized = AccountComponentMetadata::from_toml(&serialized).unwrap();
384 assert_eq!(deserialized, original_config);
385 }
386
387 #[test]
388 fn new_non_contiguous_value_slots() {
389 let storage = vec![
390 StorageEntry::new_value(0, default_felt_array()),
391 StorageEntry::new_value(2, default_felt_array()),
392 ];
393
394 let result = AccountComponentMetadata::new(
395 "test".into(),
396 "desc".into(),
397 Version::parse("0.1.0").unwrap(),
398 BTreeSet::new(),
399 storage,
400 );
401 assert_matches!(result, Err(AccountComponentTemplateError::NonContiguousSlots(0, 2)));
402 }
403
404 #[test]
405 fn binary_serde_roundtrip() {
406 let storage = vec![
407 StorageEntry::new_multislot(
408 FieldIdentifier::with_name(StorageValueName::new("slot1").unwrap()),
409 1..3,
410 vec![default_felt_array(), default_felt_array()],
411 ),
412 StorageEntry::new_value(0, default_felt_array()),
413 ];
414
415 let component_metadata = AccountComponentMetadata {
416 name: "test".into(),
417 description: "desc".into(),
418 version: Version::parse("0.1.0").unwrap(),
419 supported_types: BTreeSet::new(),
420 storage,
421 };
422
423 let library = Assembler::default().assemble_library([CODE]).unwrap();
424 let template = AccountComponentTemplate::new(component_metadata, library);
425 let _ = AccountComponent::from_template(&template, &InitStorageData::default()).unwrap();
426
427 let serialized = template.to_bytes();
428 let deserialized = AccountComponentTemplate::read_from_bytes(&serialized).unwrap();
429
430 assert_eq!(deserialized, template);
431 }
432
433 #[test]
434 pub fn fail_on_duplicate_key() {
435 let toml_text = r#"
436 name = "Test Component"
437 description = "This is a test component"
438 version = "1.0.1"
439 supported-types = ["FungibleFaucet"]
440
441 [[storage]]
442 name = "map"
443 description = "A storage map entry"
444 slot = 0
445 values = [
446 { key = "0x1", value = ["0x3", "0x1", "0x2", "0x3"] },
447 { key = "0x1", value = ["0x1", "0x2", "0x3", "0x10"] }
448 ]
449 "#;
450
451 let result = AccountComponentMetadata::from_toml(toml_text);
452 assert_matches!(result, Err(AccountComponentTemplateError::StorageMapHasDuplicateKeys(_)));
453 }
454
455 #[test]
456 pub fn fail_on_duplicate_placeholder_name() {
457 let toml_text = r#"
458 name = "Test Component"
459 description = "tests for two duplicate placeholders"
460 version = "1.0.1"
461 supported-types = ["FungibleFaucet"]
462
463 [[storage]]
464 name = "map"
465 slot = 0
466 values = [
467 { key = "0x1", value = [{type = "felt", name = "test"}, "0x1", "0x2", "0x3"] },
468 { key = "0x2", value = ["0x1", "0x2", "0x3", {type = "token_symbol", name = "test"}] }
469 ]
470 "#;
471
472 let result = AccountComponentMetadata::from_toml(toml_text).unwrap_err();
473 assert_matches::assert_matches!(
474 result,
475 AccountComponentTemplateError::DuplicatePlaceholderName(_)
476 );
477 }
478
479 #[test]
480 pub fn fail_duplicate_key_instance() {
481 let _ = color_eyre::install();
482
483 let toml_text = r#"
484 name = "Test Component"
485 description = "This is a test component"
486 version = "1.0.1"
487 supported-types = ["FungibleFaucet"]
488
489 [[storage]]
490 name = "map"
491 description = "A storage map entry"
492 slot = 0
493 values = [
494 { key = ["0", "0", "0", "1"], value = ["0x9", "0x12", "0x31", "0x18"] },
495 { key = { name="duplicate_key" }, value = ["0x1", "0x2", "0x3", "0x4"] }
496 ]
497 "#;
498
499 let metadata = AccountComponentMetadata::from_toml(toml_text).unwrap();
500 let library = Assembler::default().assemble_library([CODE]).unwrap();
501 let template = AccountComponentTemplate::new(metadata, library);
502
503 let init_storage_data = InitStorageData::new(
506 [(
507 StorageValueName::new("map.duplicate_key").unwrap(),
508 "0x0000000000000000000000000000000000000000000000000100000000000000".to_string(),
509 )],
510 BTreeMap::new(),
511 );
512 let account_component = AccountComponent::from_template(&template, &init_storage_data);
513 assert_matches!(
514 account_component,
515 Err(AccountError::AccountComponentTemplateInstantiationError(
516 AccountComponentTemplateError::StorageMapHasDuplicateKeys(_)
517 ))
518 );
519
520 let valid_init_storage_data = InitStorageData::new(
522 [(StorageValueName::new("map.duplicate_key").unwrap(), "0x30".to_string())],
523 BTreeMap::new(),
524 );
525 AccountComponent::from_template(&template, &valid_init_storage_data).unwrap();
526 }
527}