1use alloc::{boxed::Box, string::String, vec::Vec};
2
3use vm_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
4use vm_processor::DeserializationError;
5
6mod entry_content;
7pub use entry_content::*;
8
9use super::AccountComponentTemplateError;
10use crate::account::StorageSlot;
11
12mod placeholder;
13pub use placeholder::{PlaceholderType, StoragePlaceholder, StorageValue};
14
15mod init_storage_data;
16pub use init_storage_data::InitStorageData;
17
18#[cfg(feature = "std")]
19pub mod toml;
20
21#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum StorageEntry {
33 Value {
35 name: String,
37 description: Option<String>,
39 slot: u8,
41 value: WordRepresentation,
43 },
44
45 Map {
47 name: String,
49 description: Option<String>,
51 slot: u8,
53 map: MapRepresentation,
55 },
56
57 MultiSlot {
59 name: String,
61 description: Option<String>,
63 slots: Vec<u8>,
65 values: Vec<WordRepresentation>,
67 },
68}
69
70impl StorageEntry {
71 pub fn new_value(
73 name: impl Into<String>,
74 description: Option<impl Into<String>>,
75 slot: u8,
76 value: impl Into<WordRepresentation>,
77 ) -> Self {
78 StorageEntry::Value {
79 name: name.into(),
80 description: description.map(Into::<String>::into),
81 slot,
82 value: value.into(),
83 }
84 }
85
86 pub fn new_map(
88 name: impl Into<String>,
89 description: Option<impl Into<String>>,
90 slot: u8,
91 map_representation: MapRepresentation,
92 ) -> Result<Self, AccountComponentTemplateError> {
93 let entry = StorageEntry::Map {
94 name: name.into(),
95 description: description.map(Into::<String>::into),
96 slot,
97 map: map_representation,
98 };
99
100 entry.validate()?;
101 Ok(entry)
102 }
103
104 pub fn new_multi_slot(
106 name: impl Into<String>,
107 description: Option<impl Into<String>>,
108 slots: Vec<u8>,
109 values: Vec<impl Into<WordRepresentation>>,
110 ) -> Result<Self, AccountComponentTemplateError> {
111 let entry = StorageEntry::MultiSlot {
112 name: name.into(),
113 description: description.map(Into::<String>::into),
114 slots,
115 values: values.into_iter().map(Into::into).collect(),
116 };
117
118 entry.validate()?;
119 Ok(entry)
120 }
121
122 pub fn slot_indices(&self) -> &[u8] {
124 match self {
125 StorageEntry::MultiSlot { slots, .. } => slots.as_slice(),
126 StorageEntry::Value { slot, .. } => core::slice::from_ref(slot),
127 StorageEntry::Map { slot, .. } => core::slice::from_ref(slot),
128 }
129 }
130
131 pub fn name(&self) -> &str {
133 match self {
134 StorageEntry::Value { name, .. } => name.as_str(),
135 StorageEntry::Map { name, .. } => name.as_str(),
136 StorageEntry::MultiSlot { name, .. } => name.as_str(),
137 }
138 }
139
140 pub fn description(&self) -> Option<&str> {
142 match self {
143 StorageEntry::Value { description, .. } => description.as_deref(),
144 StorageEntry::Map { description, .. } => description.as_deref(),
145 StorageEntry::MultiSlot { description, .. } => description.as_deref(),
146 }
147 }
148
149 pub fn word_values(&self) -> &[WordRepresentation] {
154 match self {
155 StorageEntry::Value { value, .. } => core::slice::from_ref(value),
156 StorageEntry::MultiSlot { values, .. } => values.as_slice(),
157 StorageEntry::Map { .. } => &[],
158 }
159 }
160
161 pub fn all_placeholders_iter(
164 &self,
165 ) -> Box<dyn Iterator<Item = (&StoragePlaceholder, PlaceholderType)> + '_> {
166 match self {
167 StorageEntry::Value { value, .. } => value.all_placeholders_iter(),
168 StorageEntry::Map { map: map_entries, .. } => map_entries.all_placeholders_iter(),
169 StorageEntry::MultiSlot { values, .. } => {
170 Box::new(values.iter().flat_map(|word| word.all_placeholders_iter()))
171 },
172 }
173 }
174
175 pub fn try_build_storage_slots(
184 &self,
185 init_storage_data: &InitStorageData,
186 ) -> Result<Vec<StorageSlot>, AccountComponentTemplateError> {
187 match self {
188 StorageEntry::Value { value, .. } => {
189 let slot = value.try_build_word(init_storage_data)?;
190 Ok(vec![StorageSlot::Value(slot)])
191 },
192 StorageEntry::Map { map: values, .. } => {
193 let storage_map = values.try_build_map(init_storage_data)?;
194 Ok(vec![StorageSlot::Map(storage_map)])
195 },
196 StorageEntry::MultiSlot { values, .. } => Ok(values
197 .iter()
198 .map(|word_repr| {
199 word_repr.clone().try_build_word(init_storage_data).map(StorageSlot::Value)
200 })
201 .collect::<Result<Vec<StorageSlot>, _>>()?),
202 }
203 }
204
205 pub(super) fn validate(&self) -> Result<(), AccountComponentTemplateError> {
207 match self {
208 StorageEntry::Map { map, .. } => map.validate(),
209 StorageEntry::MultiSlot { slots, values, .. } => {
210 if slots.len() != values.len() {
211 return Err(AccountComponentTemplateError::MultiSlotArityMismatch);
212 } else {
213 let mut all_slots = slots.clone();
214 all_slots.sort_unstable();
215 for slots in all_slots.windows(2) {
216 if slots[1] == slots[0] {
217 return Err(AccountComponentTemplateError::DuplicateSlot(slots[0]));
218 }
219
220 if slots[1] != slots[0] + 1 {
221 return Err(AccountComponentTemplateError::NonContiguousSlots(
222 slots[0], slots[1],
223 ));
224 }
225 }
226 }
227 Ok(())
228 },
229 StorageEntry::Value { .. } => Ok(()),
230 }
231 }
232}
233
234impl Serializable for StorageEntry {
238 fn write_into<W: ByteWriter>(&self, target: &mut W) {
239 match self {
240 StorageEntry::Value { name, description, slot, value } => {
241 target.write_u8(0u8);
242 target.write(name);
243 target.write(description);
244 target.write_u8(*slot);
245 target.write(value);
246 },
247 StorageEntry::Map { name, description, slot, map: values } => {
248 target.write_u8(1u8);
249 target.write(name);
250 target.write(description);
251 target.write_u8(*slot);
252 target.write(values);
253 },
254 StorageEntry::MultiSlot { name, description, slots, values } => {
255 target.write_u8(2u8);
256 target.write(name);
257 target.write(description);
258 target.write(slots);
259 target.write(values);
260 },
261 }
262 }
263}
264
265impl Deserializable for StorageEntry {
266 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
267 let variant_tag = source.read_u8()?;
268 let name: String = source.read()?;
269 let description: Option<String> = source.read()?;
270
271 match variant_tag {
272 0 => {
274 let slot = source.read_u8()?;
275 let value: WordRepresentation = source.read()?;
276
277 Ok(StorageEntry::Value { name, description, slot, value })
278 },
279
280 1 => {
282 let slot = source.read_u8()?;
283 let map_representation: MapRepresentation = source.read()?;
284
285 Ok(StorageEntry::Map {
286 name,
287 description,
288 slot,
289 map: map_representation,
290 })
291 },
292
293 2 => {
295 let slots: Vec<u8> = source.read()?;
296 let values: Vec<WordRepresentation> = source.read()?;
297
298 Ok(StorageEntry::MultiSlot { name, description, slots, values })
299 },
300
301 _ => Err(DeserializationError::InvalidValue(format!(
303 "unknown variant tag `{variant_tag}` for StorageEntry"
304 ))),
305 }
306 }
307}
308
309#[derive(Debug, Clone, PartialEq, Eq)]
314#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
315pub struct MapEntry {
316 key: WordRepresentation,
317 value: WordRepresentation,
318}
319
320impl MapEntry {
321 pub fn new(key: impl Into<WordRepresentation>, value: impl Into<WordRepresentation>) -> Self {
322 Self { key: key.into(), value: value.into() }
323 }
324
325 pub fn key(&self) -> &WordRepresentation {
326 &self.key
327 }
328
329 pub fn value(&self) -> &WordRepresentation {
330 &self.value
331 }
332
333 pub fn all_placeholders_iter(
336 &self,
337 ) -> impl Iterator<Item = (&StoragePlaceholder, PlaceholderType)> {
338 self.key.all_placeholders_iter().chain(self.value.all_placeholders_iter())
339 }
340
341 pub fn into_parts(self) -> (WordRepresentation, WordRepresentation) {
342 let MapEntry { key, value } = self;
343 (key, value)
344 }
345}
346
347impl Serializable for MapEntry {
348 fn write_into<W: ByteWriter>(&self, target: &mut W) {
349 self.key.write_into(target);
350 self.value.write_into(target);
351 }
352}
353
354impl Deserializable for MapEntry {
355 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
356 let key = WordRepresentation::read_from(source)?;
357 let value = WordRepresentation::read_from(source)?;
358 Ok(MapEntry { key, value })
359 }
360}
361
362#[cfg(test)]
366mod tests {
367 use core::panic;
368 use std::collections::BTreeSet;
369
370 use assembly::Assembler;
371 use assert_matches::assert_matches;
372 use semver::Version;
373 use vm_core::{Felt, FieldElement};
374
375 use super::*;
376 use crate::{
377 account::{
378 component::template::{AccountComponentMetadata, AccountComponentTemplate},
379 AccountComponent, AccountType, StorageMap,
380 },
381 digest,
382 testing::account_code::CODE,
383 AccountError,
384 };
385
386 #[test]
387 fn test_storage_entry_serialization() {
388 let array = [
389 FeltRepresentation::Decimal(Felt::new(0xabc)),
390 FeltRepresentation::Decimal(Felt::new(1218)),
391 FeltRepresentation::Hexadecimal(Felt::new(0xdba3)),
392 FeltRepresentation::Template(StoragePlaceholder::new("test.array.dyn").unwrap()),
393 ];
394 let storage = vec![
395 StorageEntry::Value {
396 name: "slot0".into(),
397 description: Some("First slot".into()),
398 slot: 0,
399 value: WordRepresentation::Value(digest!("0x333123").into()),
400 },
401 StorageEntry::Map {
402 name: "map".into(),
403 description: Some("A storage map entry".into()),
404 slot: 1,
405 map: MapRepresentation::List(vec![
406 MapEntry {
407 key: WordRepresentation::Template(
408 StoragePlaceholder::new("foo.bar").unwrap(),
409 ),
410 value: WordRepresentation::Value(digest!("0x2").into()),
411 },
412 MapEntry {
413 key: WordRepresentation::Value(digest!("0x2").into()),
414 value: WordRepresentation::Template(
415 StoragePlaceholder::new("bar.baz").unwrap(),
416 ),
417 },
418 MapEntry {
419 key: WordRepresentation::Value(digest!("0x3").into()),
420 value: WordRepresentation::Value(digest!("0x4").into()),
421 },
422 ]),
423 },
424 StorageEntry::MultiSlot {
425 name: "multi".into(),
426 description: Some("Multi slot entry".into()),
427 slots: vec![2, 3, 4],
428 values: vec![
429 WordRepresentation::Template(StoragePlaceholder::new("test.Template").unwrap()),
430 WordRepresentation::Array(array),
431 WordRepresentation::Value(digest!("0xabcdef123abcdef123").into()),
432 ],
433 },
434 StorageEntry::Value {
435 name: "single-slot".into(),
436 description: Some("Slot with storage placeholder".into()),
437 slot: 5,
438 value: WordRepresentation::Template(
439 StoragePlaceholder::new("single-slot-key").unwrap(),
440 ),
441 },
442 ];
443
444 let config = AccountComponentMetadata {
445 name: "Test Component".into(),
446 description: "This is a test component".into(),
447 version: Version::parse("1.0.0").unwrap(),
448 targets: BTreeSet::from([AccountType::FungibleFaucet]),
449 storage,
450 };
451
452 let toml = config.as_toml().unwrap();
453
454 let deserialized = AccountComponentMetadata::from_toml(&toml).unwrap();
455
456 assert_eq!(deserialized, config);
457 }
458
459 #[test]
460 pub fn test_toml() {
461 let toml_text = r#"
462 name = "Test Component"
463 description = "This is a test component"
464 version = "1.0.1"
465 targets = ["FungibleFaucet", "RegularAccountImmutableCode"]
466
467 [[storage]]
468 name = "map"
469 description = "A storage map entry"
470 slot = 0
471 values = [
472 { key = "0x1", value = ["{{value.test}}", "0x1", "0x2", "0x3"] },
473 { key = "{{map.key.test}}", value = "0x3" },
474 { key = "0x3", value = "0x4" }
475 ]
476
477 [[storage]]
478 name = "test-word"
479 description = "word"
480 slot = 1
481 value = "{{word.test}}"
482
483 [[storage]]
484 name = "multitest"
485 description = "a multi slot test"
486 slots = [2, 3]
487 values = [
488 "{{word.test}}",
489 ["1", "0", "0", "0"],
490 ]
491
492 [[storage]]
493 name = "map-template"
494 description = "a templated map"
495 slot = 4
496 values = "{{map.template}}"
497 "#;
498
499 let component_metadata = AccountComponentMetadata::from_toml(toml_text).unwrap();
500 for (key, placeholder_type) in component_metadata.get_unique_storage_placeholders() {
501 match key.inner() {
502 "map.key.test" | "word.test" => assert_eq!(placeholder_type, PlaceholderType::Word),
503 "value.test" => assert_eq!(placeholder_type, PlaceholderType::Felt),
504 "map.template" => assert_eq!(placeholder_type, PlaceholderType::Map),
505 _ => panic!("all cases are covered"),
506 }
507 }
508
509 let library = Assembler::default().assemble_library([CODE]).unwrap();
510
511 let template = AccountComponentTemplate::new(component_metadata, library);
512
513 let template_bytes = template.to_bytes();
514 let template_deserialized =
515 AccountComponentTemplate::read_from_bytes(&template_bytes).unwrap();
516 assert_eq!(template, template_deserialized);
517
518 let storage_placeholders = InitStorageData::new([
519 (
520 StoragePlaceholder::new("map.key.test").unwrap(),
521 StorageValue::Word(Default::default()),
522 ),
523 (
524 StoragePlaceholder::new("value.test").unwrap(),
525 StorageValue::Felt(Felt::new(64)),
526 ),
527 (
528 StoragePlaceholder::new("word.test").unwrap(),
529 StorageValue::Word([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::new(128)]),
530 ),
531 (
532 StoragePlaceholder::new("map.template").unwrap(),
533 StorageValue::Map(StorageMap::default()),
534 ),
535 ]);
536
537 let component = AccountComponent::from_template(&template, &storage_placeholders).unwrap();
538
539 assert_eq!(
540 component.supported_types(),
541 &[AccountType::FungibleFaucet, AccountType::RegularAccountImmutableCode]
542 .into_iter()
543 .collect()
544 );
545
546 let storage_map = component.storage_slots.first().unwrap();
547 match storage_map {
548 StorageSlot::Map(storage_map) => assert_eq!(storage_map.entries().count(), 3),
549 _ => panic!("should be map"),
550 }
551
552 let value_entry = component.storage_slots().get(1).unwrap();
553 match value_entry {
554 StorageSlot::Value(v) => {
555 assert_eq!(v, &[Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::new(128)])
556 },
557 _ => panic!("should be value"),
558 }
559
560 let failed_instantiation =
561 AccountComponent::from_template(&template, &InitStorageData::default());
562 assert_matches!(
563 failed_instantiation,
564 Err(AccountError::AccountComponentTemplateInstantiationError(
565 AccountComponentTemplateError::PlaceholderValueNotProvided(_)
566 ))
567 );
568 }
569
570 #[test]
571 pub fn fail_placeholder_type_mismatch() {
572 let toml_text = r#"
573 name = "Test Component"
574 description = "This is a test component"
575 version = "1.0.1"
576 targets = ["FungibleFaucet"]
577
578 [[storage]]
579 name = "map"
580 description = "A storage map entry"
581 slot = 0
582 values = [
583 { key = "0x1", value = ["{{value.test}}", "0x1", "0x2", "0x3"] },
584 ]
585
586 [[storage]]
587 name = "word"
588 slot = 1
589 value = "{{value.test}}"
590 "#;
591 let component_metadata = AccountComponentMetadata::from_toml(toml_text);
592 assert_matches!(
593 component_metadata,
594 Err(AccountComponentTemplateError::StoragePlaceholderTypeMismatch(_, _, _))
595 );
596 }
597}