miden_objects/account/component/template/storage/
mod.rs

1use alloc::{boxed::Box, string::String, vec::Vec};
2use core::{iter, ops::Range};
3
4use vm_core::{
5    Felt, FieldElement,
6    utils::{ByteReader, ByteWriter, Deserializable, Serializable},
7};
8use vm_processor::DeserializationError;
9
10mod entry_content;
11pub use entry_content::*;
12
13use super::AccountComponentTemplateError;
14use crate::account::StorageSlot;
15
16mod placeholder;
17pub use placeholder::{
18    PlaceholderTypeRequirement, StorageValueName, StorageValueNameError, TemplateType,
19    TemplateTypeError,
20};
21
22mod init_storage_data;
23pub use init_storage_data::InitStorageData;
24
25#[cfg(feature = "std")]
26pub mod toml;
27
28/// Alias used for iterators that collect all placeholders and their types within a component
29/// template.
30pub type TemplateRequirementsIter<'a> =
31    Box<dyn Iterator<Item = (StorageValueName, PlaceholderTypeRequirement)> + 'a>;
32
33// STORAGE ENTRY
34// ================================================================================================
35
36/// Represents a single entry in the component's storage layout.
37///
38/// Each entry can describe:
39/// - A value slot with a single word.
40/// - A map slot with a key-value map that occupies one storage slot.
41/// - A multi-slot entry spanning multiple contiguous slots with multiple words (but not maps) that
42///   represent a single logical value.
43#[derive(Debug, Clone, PartialEq, Eq)]
44#[allow(clippy::large_enum_variant)]
45pub enum StorageEntry {
46    /// A value slot, which can contain one word.
47    Value {
48        /// The numeric index of this map slot in the component's storage.
49        slot: u8,
50        /// An description of a word, representing either a predefined value or a templated one.
51        word_entry: WordRepresentation,
52    },
53
54    /// A map slot, containing multiple key-value pairs. Keys and values are hex-encoded strings.
55    Map {
56        /// The numeric index of this map slot in the component's storage.
57        slot: u8,
58        /// A list of key-value pairs to initialize in this map slot.
59        map: MapRepresentation,
60    },
61
62    /// A multi-slot entry, representing a single logical value across multiple slots.
63    MultiSlot {
64        /// The human-readable name of this multi-slot entry.
65        name: StorageValueName,
66        /// An optional description for the slot, explaining its purpose.
67        description: Option<String>,
68        /// The indices of the slots that form this multi-slot entry.
69        slots: Range<u8>,
70        /// A list of values to fill the logical slot, with a length equal to the amount of slots.
71        values: Vec<[FeltRepresentation; 4]>,
72    },
73}
74
75impl StorageEntry {
76    pub fn new_value(slot: u8, word_entry: impl Into<WordRepresentation>) -> Self {
77        StorageEntry::Value { slot, word_entry: word_entry.into() }
78    }
79
80    pub fn new_map(slot: u8, map: MapRepresentation) -> Self {
81        StorageEntry::Map { slot, map }
82    }
83
84    pub fn new_multislot(
85        name: impl Into<StorageValueName>,
86        description: Option<String>,
87        slots: Range<u8>,
88        values: Vec<[FeltRepresentation; 4]>,
89    ) -> Self {
90        StorageEntry::MultiSlot {
91            name: name.into(),
92            description,
93            slots,
94            values,
95        }
96    }
97
98    pub fn name(&self) -> Option<&StorageValueName> {
99        match self {
100            StorageEntry::Value { word_entry, .. } => word_entry.name(),
101            StorageEntry::Map { map, .. } => Some(map.name()),
102            StorageEntry::MultiSlot { name, .. } => Some(name),
103        }
104    }
105
106    /// Returns the slot indices that the storage entry covers.
107    pub fn slot_indices(&self) -> Box<dyn Iterator<Item = u8> + '_> {
108        match self {
109            StorageEntry::MultiSlot { slots, .. } => Box::new(slots.clone()),
110            StorageEntry::Value { slot, .. } => Box::new(iter::once(*slot)),
111            StorageEntry::Map { slot, .. } => Box::new(iter::once(*slot)),
112        }
113    }
114
115    /// Returns an iterator over all of the storage entries's value names, alongside their
116    /// expected type.
117    pub fn template_requirements(&self) -> TemplateRequirementsIter {
118        match self {
119            StorageEntry::Value { word_entry, .. } => {
120                word_entry.template_requirements(StorageValueName::empty())
121            },
122            StorageEntry::Map { map: map_entries, .. } => map_entries.template_requirements(),
123            StorageEntry::MultiSlot { values, name, .. } => {
124                Box::new(values.iter().flat_map(move |word| {
125                    word.iter().flat_map(move |f| f.template_requirements(name.clone()))
126                }))
127            },
128        }
129    }
130
131    /// Attempts to convert the storage entry into a list of [`StorageSlot`].
132    ///
133    /// - [`StorageEntry::Value`] would convert to a [`StorageSlot::Value`]
134    /// - [`StorageEntry::MultiSlot`] would convert to as many [`StorageSlot::Value`] as required by
135    ///   the defined type
136    /// - [`StorageEntry::Map`] would convert to a [`StorageSlot::Map`]
137    ///
138    /// Each of the entry's values could be templated. These values are replaced for values found
139    /// in `init_storage_data`, identified by its key.
140    pub fn try_build_storage_slots(
141        &self,
142        init_storage_data: &InitStorageData,
143    ) -> Result<Vec<StorageSlot>, AccountComponentTemplateError> {
144        match self {
145            StorageEntry::Value { word_entry, .. } => {
146                let slot =
147                    word_entry.try_build_word(init_storage_data, StorageValueName::empty())?;
148                Ok(vec![StorageSlot::Value(slot)])
149            },
150            StorageEntry::Map { map: values, .. } => {
151                let storage_map = values.try_build_map(init_storage_data)?;
152                Ok(vec![StorageSlot::Map(storage_map)])
153            },
154            StorageEntry::MultiSlot { values, name, .. } => Ok(values
155                .iter()
156                .map(|word_repr| {
157                    let mut result = [Felt::ZERO; 4];
158
159                    for (index, felt_repr) in word_repr.iter().enumerate() {
160                        result[index] =
161                            felt_repr.try_build_felt(init_storage_data, name.clone())?;
162                    }
163                    // SAFETY: result is guaranteed to have all its 4 indices rewritten
164                    Ok(StorageSlot::Value(result))
165                })
166                .collect::<Result<Vec<StorageSlot>, _>>()?),
167        }
168    }
169
170    /// Validates the storage entry for internal consistency.
171    pub(super) fn validate(&self) -> Result<(), AccountComponentTemplateError> {
172        match self {
173            StorageEntry::Map { map, .. } => map.validate(),
174            StorageEntry::MultiSlot { slots, values, .. } => {
175                if slots.len() == 1 {
176                    return Err(AccountComponentTemplateError::MultiSlotSpansOneSlot);
177                }
178
179                if slots.len() != values.len() {
180                    return Err(AccountComponentTemplateError::MultiSlotArityMismatch);
181                }
182
183                for slot_word in values {
184                    for felt_in_slot in slot_word {
185                        felt_in_slot.validate()?;
186                    }
187                }
188                Ok(())
189            },
190            StorageEntry::Value { word_entry, .. } => Ok(word_entry.validate()?),
191        }
192    }
193}
194
195// SERIALIZATION
196// ================================================================================================
197
198impl Serializable for StorageEntry {
199    fn write_into<W: ByteWriter>(&self, target: &mut W) {
200        match self {
201            StorageEntry::Value { slot, word_entry } => {
202                target.write_u8(0u8);
203                target.write_u8(*slot);
204                target.write(word_entry);
205            },
206            StorageEntry::Map { slot, map, .. } => {
207                target.write_u8(1u8);
208                target.write_u8(*slot);
209                target.write(map);
210            },
211            StorageEntry::MultiSlot { name, description, slots, values } => {
212                target.write_u8(2u8);
213                target.write(name);
214                target.write(description);
215                target.write(slots.start);
216                target.write(slots.end);
217                target.write(values);
218            },
219        }
220    }
221}
222
223impl Deserializable for StorageEntry {
224    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
225        let variant_tag = source.read_u8()?;
226        match variant_tag {
227            0 => {
228                let slot = source.read_u8()?;
229                let word_entry: WordRepresentation = source.read()?;
230                Ok(StorageEntry::Value { slot, word_entry })
231            },
232            1 => {
233                let slot = source.read_u8()?;
234                let map: MapRepresentation = source.read()?;
235                Ok(StorageEntry::Map { slot, map })
236            },
237            2 => {
238                let name: StorageValueName = source.read()?;
239                let description: Option<String> = source.read()?;
240                let slots_start: u8 = source.read()?;
241                let slots_end: u8 = source.read()?;
242                let values: Vec<[FeltRepresentation; 4]> = source.read()?;
243                Ok(StorageEntry::MultiSlot {
244                    name,
245                    description,
246                    slots: slots_start..slots_end,
247                    values,
248                })
249            },
250            _ => Err(DeserializationError::InvalidValue(format!(
251                "unknown variant tag `{}` for StorageEntry",
252                variant_tag
253            ))),
254        }
255    }
256}
257
258// MAP ENTRY
259// ================================================================================================
260
261/// Key-value entry for storage maps.
262#[derive(Debug, Clone, PartialEq, Eq)]
263#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
264pub struct MapEntry {
265    key: WordRepresentation,
266    value: WordRepresentation,
267}
268
269impl MapEntry {
270    pub fn new(key: impl Into<WordRepresentation>, value: impl Into<WordRepresentation>) -> Self {
271        Self { key: key.into(), value: value.into() }
272    }
273
274    pub fn key(&self) -> &WordRepresentation {
275        &self.key
276    }
277
278    pub fn value(&self) -> &WordRepresentation {
279        &self.value
280    }
281
282    pub fn into_parts(self) -> (WordRepresentation, WordRepresentation) {
283        let MapEntry { key, value } = self;
284        (key, value)
285    }
286
287    pub fn template_requirements(
288        &self,
289        placeholder_prefix: StorageValueName,
290    ) -> TemplateRequirementsIter<'_> {
291        let key_iter = self.key.template_requirements(placeholder_prefix.clone());
292        let value_iter = self.value.template_requirements(placeholder_prefix);
293
294        Box::new(key_iter.chain(value_iter))
295    }
296}
297
298impl Serializable for MapEntry {
299    fn write_into<W: ByteWriter>(&self, target: &mut W) {
300        self.key.write_into(target);
301        self.value.write_into(target);
302    }
303}
304
305impl Deserializable for MapEntry {
306    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
307        let key = WordRepresentation::read_from(source)?;
308        let value = WordRepresentation::read_from(source)?;
309        Ok(MapEntry { key, value })
310    }
311}
312
313// TESTS
314// ================================================================================================
315
316#[cfg(test)]
317mod tests {
318    use core::{error::Error, panic};
319    use std::string::ToString;
320
321    use assembly::Assembler;
322    use semver::Version;
323    use vm_core::{
324        Felt, FieldElement, Word,
325        utils::{Deserializable, Serializable},
326    };
327
328    use crate::{
329        AccountError,
330        account::{
331            AccountComponent, AccountComponentTemplate, AccountType, FeltRepresentation,
332            StorageEntry, StorageSlot, TemplateTypeError, WordRepresentation,
333            component::template::{
334                AccountComponentMetadata, InitStorageData, MapEntry, MapRepresentation,
335                StorageValueName, storage::placeholder::TemplateType,
336            },
337        },
338        digest,
339        errors::AccountComponentTemplateError,
340        testing::account_code::CODE,
341    };
342
343    #[test]
344    fn test_storage_entry_serialization() {
345        let felt_array: [FeltRepresentation; 4] = [
346            FeltRepresentation::from(Felt::new(0xabc)),
347            FeltRepresentation::from(Felt::new(1218)),
348            FeltRepresentation::from(Felt::new(0xdba3)),
349            FeltRepresentation::new_template(
350                TemplateType::native_felt(),
351                StorageValueName::new("slot3").unwrap(),
352            )
353            .with_description("dummy description"),
354        ];
355
356        let test_word: Word = digest!("0x000001").into();
357        let test_word = test_word.map(FeltRepresentation::from);
358
359        let map_representation = MapRepresentation::new(
360            vec![
361                MapEntry {
362                    key: WordRepresentation::new_template(
363                        TemplateType::native_word(),
364                        StorageValueName::new("foo").unwrap(),
365                    ),
366                    value: WordRepresentation::new_value(test_word.clone(), None),
367                },
368                MapEntry {
369                    key: WordRepresentation::new_value(test_word.clone(), None),
370                    value: WordRepresentation::new_template(
371                        TemplateType::native_word(),
372                        StorageValueName::new("bar").unwrap(),
373                    )
374                    .with_description("bar description"),
375                },
376                MapEntry {
377                    key: WordRepresentation::new_template(
378                        TemplateType::native_word(),
379                        StorageValueName::new("baz").unwrap(),
380                    )
381                    .with_description("baz description"),
382                    value: WordRepresentation::new_value(test_word, None),
383                },
384            ],
385            StorageValueName::new("map").unwrap(),
386        )
387        .with_description("a storage map description");
388
389        let storage = vec![
390            StorageEntry::new_value(0, felt_array.clone()),
391            StorageEntry::new_map(1, map_representation),
392            StorageEntry::new_multislot(
393                StorageValueName::new("multi").unwrap(),
394                Some("Multi slot entry".into()),
395                2..4,
396                vec![
397                    [
398                        FeltRepresentation::new_template(
399                            TemplateType::native_felt(),
400                            StorageValueName::new("test").unwrap(),
401                        ),
402                        FeltRepresentation::new_template(
403                            TemplateType::native_felt(),
404                            StorageValueName::new("test2").unwrap(),
405                        ),
406                        FeltRepresentation::new_template(
407                            TemplateType::native_felt(),
408                            StorageValueName::new("test3").unwrap(),
409                        ),
410                        FeltRepresentation::new_template(
411                            TemplateType::native_felt(),
412                            StorageValueName::new("test4").unwrap(),
413                        ),
414                    ],
415                    felt_array,
416                ],
417            ),
418            StorageEntry::new_value(
419                4,
420                WordRepresentation::new_template(
421                    TemplateType::native_word(),
422                    StorageValueName::new("single").unwrap(),
423                ),
424            ),
425        ];
426
427        let config = AccountComponentMetadata {
428            name: "Test Component".into(),
429            description: "This is a test component".into(),
430            version: Version::parse("1.0.0").unwrap(),
431            supported_types: std::collections::BTreeSet::from([AccountType::FungibleFaucet]),
432            storage,
433        };
434        let toml = config.as_toml().unwrap();
435        let deserialized = AccountComponentMetadata::from_toml(&toml).unwrap();
436
437        assert_eq!(deserialized, config);
438    }
439
440    #[test]
441    pub fn toml_serde_roundtrip() {
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", "RegularAccountImmutableCode"]
447
448        [[storage]]
449        name = "map_entry"
450        slot = 0
451        values = [
452            { key = "0x1", value = ["0x1","0x2","0x3","0"]},
453            { key = "0x3", value = "0x123" }, 
454            { key = { name = "map_key_template", description = "this tests that the default type is correctly set"}, value = "0x3" },
455        ]
456
457        [[storage]]
458        name = "token_metadata"
459        description = "Contains metadata about the token associated to the faucet account"
460        slot = 1
461        value = [
462            { type = "felt", name = "max_supply", description = "Maximum supply of the token in base units" }, # placeholder
463            { type = "token_symbol", value = "TST" }, # hardcoded non-felt type
464            { type = "u8", name = "decimals", description = "Number of decimal places" }, # placeholder
465            { value = "0" }, 
466        ]
467
468        [[storage]]
469        name = "default_recallable_height"
470        slot = 2
471        type = "word"
472        "#;
473
474        let component_metadata = AccountComponentMetadata::from_toml(toml_text).unwrap();
475        let requirements = component_metadata.get_placeholder_requirements();
476
477        assert_eq!(requirements.len(), 4);
478
479        let supply = requirements
480            .get(&StorageValueName::new("token_metadata.max_supply").unwrap())
481            .unwrap();
482        assert_eq!(supply.r#type.as_str(), "felt");
483
484        let decimals = requirements
485            .get(&StorageValueName::new("token_metadata.decimals").unwrap())
486            .unwrap();
487        assert_eq!(decimals.r#type.as_str(), "u8");
488
489        let default_recallable_height = requirements
490            .get(&StorageValueName::new("default_recallable_height").unwrap())
491            .unwrap();
492        assert_eq!(default_recallable_height.r#type.as_str(), "word");
493
494        let map_key_template = requirements
495            .get(&StorageValueName::new("map_entry.map_key_template").unwrap())
496            .unwrap();
497        assert_eq!(map_key_template.r#type.as_str(), "word");
498
499        let library = Assembler::default().assemble_library([CODE]).unwrap();
500        let template = AccountComponentTemplate::new(component_metadata, library);
501
502        let template_bytes = template.to_bytes();
503        let template_deserialized =
504            AccountComponentTemplate::read_from_bytes(&template_bytes).unwrap();
505        assert_eq!(template, template_deserialized);
506
507        // Fail to parse because 2800 > u8
508        let storage_placeholders = InitStorageData::new([
509            (
510                StorageValueName::new("map_entry.map_key_template").unwrap(),
511                "0x123".to_string(),
512            ),
513            (
514                StorageValueName::new("token_metadata.max_supply").unwrap(),
515                20_000u64.to_string(),
516            ),
517            (StorageValueName::new("token_metadata.decimals").unwrap(), "2800".into()),
518            (StorageValueName::new("default_recallable_height").unwrap(), "0".into()),
519        ]);
520
521        let component = AccountComponent::from_template(&template, &storage_placeholders);
522        assert_matches::assert_matches!(
523            component,
524            Err(AccountError::AccountComponentTemplateInstantiationError(
525                AccountComponentTemplateError::StorageValueParsingError(
526                    TemplateTypeError::ParseError { .. }
527                )
528            ))
529        );
530
531        // Instantiate successfully
532        let storage_placeholders = InitStorageData::new([
533            (
534                StorageValueName::new("map_entry.map_key_template").unwrap(),
535                "0x123".to_string(),
536            ),
537            (
538                StorageValueName::new("token_metadata.max_supply").unwrap(),
539                20_000u64.to_string(),
540            ),
541            (StorageValueName::new("token_metadata.decimals").unwrap(), "128".into()),
542            (StorageValueName::new("default_recallable_height").unwrap(), "0x0".into()),
543        ]);
544
545        let component = AccountComponent::from_template(&template, &storage_placeholders).unwrap();
546        assert_eq!(
547            component.supported_types(),
548            &[AccountType::FungibleFaucet, AccountType::RegularAccountImmutableCode]
549                .into_iter()
550                .collect()
551        );
552
553        let storage_map = component.storage_slots.first().unwrap();
554        match storage_map {
555            StorageSlot::Map(storage_map) => assert_eq!(storage_map.entries().count(), 3),
556            _ => panic!("should be map"),
557        }
558
559        let value_entry = component.storage_slots().get(2).unwrap();
560        match value_entry {
561            StorageSlot::Value(v) => {
562                assert_eq!(v, &[Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO])
563            },
564            _ => panic!("should be value"),
565        }
566
567        let failed_instantiation =
568            AccountComponent::from_template(&template, &InitStorageData::default());
569
570        assert_matches::assert_matches!(
571            failed_instantiation,
572            Err(AccountError::AccountComponentTemplateInstantiationError(
573                AccountComponentTemplateError::PlaceholderValueNotProvided(_)
574            ))
575        );
576    }
577
578    #[test]
579    fn test_no_duplicate_slot_names() {
580        let toml_text = r#"
581        name = "Test Component"
582        description = "This is a test component"
583        version = "1.0.1"
584        supported-types = ["FungibleFaucet", "RegularAccountImmutableCode"]
585
586        [[storage]]
587        name = "test_duplicate"
588        slot = 0
589        type = "felt" # Felt is not a valid type for word slots
590        "#;
591
592        let err = AccountComponentMetadata::from_toml(toml_text).unwrap_err();
593        assert_matches::assert_matches!(err, AccountComponentTemplateError::InvalidType(_, _))
594    }
595
596    #[test]
597    fn toml_fail_multislot_arity_mismatch() {
598        let toml_text = r#"
599        name = "Test Component"
600        description = "Test multislot arity mismatch"
601        version = "1.0.1"
602        supported-types = ["FungibleFaucet"]
603
604        [[storage]]
605        name = "multislot_test"
606        slots = [0, 1]
607        values = [
608            [ "0x1", "0x2", "0x3", "0x4" ]
609        ]
610    "#;
611
612        let err = AccountComponentMetadata::from_toml(toml_text).unwrap_err();
613        assert_matches::assert_matches!(err, AccountComponentTemplateError::MultiSlotArityMismatch);
614    }
615
616    #[test]
617    fn toml_fail_multislot_duplicate_slot() {
618        let toml_text = r#"
619        name = "Test Component"
620        description = "Test multislot duplicate slot"
621        version = "1.0.1"
622        supported-types = ["FungibleFaucet"]
623
624        [[storage]]
625        name = "multislot_duplicate"
626        slots = [0, 1]
627        values = [
628            [ "0x1", "0x2", "0x3", "0x4" ],
629            [ "0x5", "0x6", "0x7", "0x8" ]
630        ]
631
632        [[storage]]
633        name = "multislot_duplicate"
634        slots = [1, 2]
635        values = [
636            [ "0x1", "0x2", "0x3", "0x4" ],
637            [ "0x5", "0x6", "0x7", "0x8" ]
638        ]
639    "#;
640
641        let err = AccountComponentMetadata::from_toml(toml_text).unwrap_err();
642        assert_matches::assert_matches!(err, AccountComponentTemplateError::DuplicateSlot(1));
643    }
644
645    #[test]
646    fn toml_fail_multislot_non_contiguous_slots() {
647        let toml_text = r#"
648        name = "Test Component"
649        description = "Test multislot non contiguous"
650        version = "1.0.1"
651        supported-types = ["FungibleFaucet"]
652
653        [[storage]]
654        name = "multislot_non_contiguous"
655        slots = [0, 2]
656        values = [
657            [ "0x1", "0x2", "0x3", "0x4" ],
658            [ "0x5", "0x6", "0x7", "0x8" ]
659        ]
660    "#;
661
662        let err = AccountComponentMetadata::from_toml(toml_text).unwrap_err();
663        // validate inner serde error
664        assert!(err.source().unwrap().to_string().contains("are not contiguous"));
665    }
666
667    #[test]
668    fn toml_fail_duplicate_storage_entry_names() {
669        let toml_text = r#"
670        name = "Test Component"
671        description = "Component with duplicate storage entry names"
672        version = "1.0.1"
673        supported-types = ["FungibleFaucet"]
674
675        [[storage]]
676        # placeholder
677        name = "duplicate"
678        slot = 0
679        type = "word"
680
681        [[storage]]
682        name = "duplicate"
683        slot = 1
684        value = [ "0x1", "0x1", "0x1", "0x1" ]
685    "#;
686
687        let result = AccountComponentMetadata::from_toml(toml_text);
688        assert_matches::assert_matches!(
689            result.unwrap_err(),
690            AccountComponentTemplateError::DuplicateEntryNames(_)
691        );
692    }
693
694    #[test]
695    fn toml_fail_multislot_spans_one_slot() {
696        let toml_text = r#"
697        name = "Test Component"
698        description = "Test multislot spans one slot"
699        version = "1.0.1"
700        supported-types = ["RegularAccountImmutableCode"]
701
702        [[storage]]
703        name = "multislot_one_slot"
704        slots = [0]
705        values = [
706            [ "0x1", "0x2", "0x3", "0x4" ],
707        ]
708    "#;
709
710        let result = AccountComponentMetadata::from_toml(toml_text);
711        assert_matches::assert_matches!(
712            result.unwrap_err(),
713            AccountComponentTemplateError::MultiSlotSpansOneSlot
714        );
715    }
716
717    #[test]
718    fn test_toml_multislot_success() {
719        let toml_text = r#"
720        name = "Test Component"
721        description = "A multi-slot success scenario"
722        version = "1.0.1"
723        supported-types = ["FungibleFaucet"]
724
725        [[storage]]
726        name = "multi_slot_example"
727        slots = [0, 1, 2]
728        values = [
729            ["0x1", "0x2", "0x3", "0x4"],
730            ["0x5", "0x6", "0x7", "0x8"],
731            ["0x9", "0xa", "0xb", "0xc"]
732        ]
733    "#;
734
735        let metadata = AccountComponentMetadata::from_toml(toml_text).unwrap();
736        match &metadata.storage_entries()[0] {
737            StorageEntry::MultiSlot { name, slots, values, .. } => {
738                assert_eq!(name.as_str(), "multi_slot_example");
739                assert_eq!(slots, &(0..3));
740                assert_eq!(values.len(), 3);
741            },
742            _ => panic!("expected multislot"),
743        }
744    }
745}