-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Introduce high-level types for templates #1124
base: next
Are you sure you want to change the base?
Changes from 1 commit
386f794
8bfc6b0
ed3a3c6
9a4f5e4
96703f6
772a0d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
use alloc::{ | ||
collections::{btree_map::Entry, BTreeMap, BTreeSet}, | ||
collections::{BTreeMap, BTreeSet}, | ||
string::{String, ToString}, | ||
vec::Vec, | ||
}; | ||
|
@@ -109,28 +109,29 @@ impl Deserializable for AccountComponentTemplate { | |
/// # use semver::Version; | ||
/// # use std::collections::BTreeSet; | ||
/// # use miden_objects::{testing::account_code::CODE, account::{ | ||
/// # AccountComponent, AccountComponentMetadata, InitStorageData, StorageEntry, | ||
/// # StoragePlaceholder, StorageValue, | ||
/// # AccountComponent, AccountComponentMetadata, StorageEntry, | ||
/// # StorageValueName, | ||
/// # AccountComponentTemplate, FeltRepresentation, WordRepresentation}, | ||
/// # assembly::Assembler, Felt}; | ||
/// # use miden_objects::account::FeltType; | ||
/// # use miden_objects::account::InitStorageData; | ||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
/// let first_felt = FeltRepresentation::Decimal(Felt::new(0u64)); | ||
/// let second_felt = FeltRepresentation::Decimal(Felt::new(1u64)); | ||
/// let third_felt = FeltRepresentation::Decimal(Felt::new(2u64)); | ||
/// let first_felt = FeltRepresentation::from(Felt::new(0u64)); | ||
/// let second_felt = FeltRepresentation::from(Felt::new(1u64)); | ||
/// let third_felt = FeltRepresentation::from(Felt::new(2u64)); | ||
/// // Templated element: | ||
/// let last_element = FeltRepresentation::Template(StoragePlaceholder::new("foo")?); | ||
/// let last_element = | ||
/// FeltRepresentation::new_template(FeltType::Felt, StorageValueName::new("foo")?, None); | ||
/// | ||
/// let storage_entry = StorageEntry::new_value( | ||
/// "test-entry", | ||
/// Some("a test entry"), | ||
/// 0, | ||
/// WordRepresentation::Array([first_felt, second_felt, third_felt, last_element]), | ||
/// let word_representation = WordRepresentation::new_value( | ||
/// [first_felt, second_felt, third_felt, last_element], | ||
/// Some(StorageValueName::new("test_value")?), | ||
/// Some("This is a test value".into()), | ||
/// ); | ||
/// let storage_entry = StorageEntry::new_value(0, word_representation); | ||
/// | ||
/// let init_storage_data = InitStorageData::new([( | ||
/// StoragePlaceholder::new("foo")?, | ||
/// StorageValue::Felt(Felt::new(300u64)), | ||
/// )]); | ||
/// let init_storage_data = | ||
/// InitStorageData::new([(StorageValueName::new("test_value.foo")?, "300".to_string())]); | ||
/// | ||
/// let component_template = AccountComponentMetadata::new( | ||
/// "test name".into(), | ||
|
@@ -197,22 +198,21 @@ impl AccountComponentMetadata { | |
/// Retrieves a map of unique storage placeholders mapped to their expected type that require | ||
/// a value at the moment of component instantiation. | ||
/// | ||
/// These values will be used for | ||
/// initializing storage slot values, or storage map entries. For a full example on how a | ||
/// placeholder may be utilized, please refer to the docs for [AccountComponentMetadata]. | ||
/// These values will be used for initializing storage slot values, or storage map entries. | ||
/// For a full example on how a placeholder may be utilized, please refer to the docs for | ||
/// [AccountComponentMetadata]. | ||
/// | ||
/// Types for the returned storage placeholders are inferred based on their location in the | ||
/// storage layout structure. | ||
pub fn get_unique_storage_placeholders(&self) -> BTreeMap<StoragePlaceholder, PlaceholderType> { | ||
let mut placeholder_map = BTreeMap::new(); | ||
for storage_entry in &self.storage { | ||
for (placeholder, placeholder_type) in storage_entry.all_placeholders_iter() { | ||
// The constructors of this type guarantee each placeholder has the same type, so | ||
// reinserting them multiple times is fine. | ||
placeholder_map.insert(placeholder.clone(), placeholder_type); | ||
pub fn get_template_requirements(&self) -> BTreeMap<String, PlaceholderTypeRequirement> { | ||
let mut templates = BTreeMap::new(); | ||
for entry in self.storage_entries() { | ||
for (name, requirement) in entry.template_requirements() { | ||
templates.insert(name.to_string(), requirement); | ||
} | ||
} | ||
placeholder_map | ||
|
||
templates | ||
} | ||
|
||
/// Returns the name of the account component. | ||
|
@@ -275,30 +275,6 @@ impl AccountComponentMetadata { | |
} | ||
} | ||
|
||
// Check that placeholders do not appear more than once with a different type | ||
let mut placeholders = BTreeMap::new(); | ||
for storage_entry in &self.storage { | ||
for (placeholder, placeholder_type) in storage_entry.all_placeholders_iter() { | ||
match placeholders.entry(placeholder.clone()) { | ||
Entry::Occupied(entry) => { | ||
// if already exists, make sure it's the same type | ||
if *entry.get() != placeholder_type { | ||
return Err( | ||
AccountComponentTemplateError::StoragePlaceholderTypeMismatch( | ||
placeholder.clone(), | ||
*entry.get(), | ||
placeholder_type, | ||
), | ||
); | ||
} | ||
}, | ||
Entry::Vacant(slot) => { | ||
slot.insert(placeholder_type); | ||
}, | ||
} | ||
} | ||
} | ||
|
||
for entry in self.storage_entries() { | ||
entry.validate()?; | ||
} | ||
|
@@ -336,67 +312,75 @@ impl Deserializable for AccountComponentMetadata { | |
|
||
// TESTS | ||
// ================================================================================================ | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use std::{ | ||
collections::BTreeSet, | ||
string::{String, ToString}, | ||
}; | ||
|
||
use assembly::Assembler; | ||
use assert_matches::assert_matches; | ||
use storage::WordRepresentation; | ||
use vm_core::{Felt, FieldElement}; | ||
|
||
use super::*; | ||
use crate::{account::AccountComponent, testing::account_code::CODE, AccountError}; | ||
use semver::Version; | ||
use vm_core::{ | ||
utils::{Deserializable, Serializable}, | ||
Felt, FieldElement, | ||
}; | ||
|
||
use super::FeltRepresentation; | ||
// Import the new types and helpers. | ||
use crate::{ | ||
account::{ | ||
component::template::{ | ||
storage::StorageEntry, AccountComponentMetadata, AccountComponentTemplate, | ||
InitStorageData, | ||
}, | ||
AccountComponent, StorageValueName, | ||
}, | ||
errors::AccountComponentTemplateError, | ||
testing::account_code::CODE, | ||
AccountError, | ||
}; | ||
|
||
fn default_felt_array() -> [FeltRepresentation; 4] { | ||
[ | ||
FeltRepresentation::from(Felt::ZERO), | ||
FeltRepresentation::from(Felt::ZERO), | ||
FeltRepresentation::from(Felt::ZERO), | ||
FeltRepresentation::from(Felt::ZERO), | ||
] | ||
} | ||
|
||
#[test] | ||
fn test_contiguous_value_slots() { | ||
let storage = vec![ | ||
StorageEntry::Value { | ||
name: "slot0".into(), | ||
description: None, | ||
slot: 0, | ||
value: WordRepresentation::Value(Default::default()), | ||
}, | ||
StorageEntry::MultiSlot { | ||
name: "slot1".into(), | ||
description: None, | ||
slots: vec![1, 2], | ||
values: vec![ | ||
WordRepresentation::Array(Default::default()), | ||
WordRepresentation::Value(Default::default()), | ||
], | ||
}, | ||
StorageEntry::new_value(0, default_felt_array()), | ||
StorageEntry::new_multislot( | ||
StorageValueName::new("slot1").unwrap(), | ||
Some("multi-slot value of arity 2".into()), | ||
vec![1, 2], | ||
vec![default_felt_array(), default_felt_array()], | ||
), | ||
]; | ||
|
||
let original_config = AccountComponentMetadata::new( | ||
"test".into(), | ||
"desc".into(), | ||
Version::parse("0.1.0").unwrap(), | ||
BTreeSet::new(), | ||
let original_config = AccountComponentMetadata { | ||
name: "test".into(), | ||
description: "desc".into(), | ||
version: Version::parse("0.1.0").unwrap(), | ||
targets: BTreeSet::new(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Not necessarily in this PR, but should we align the naming of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Forgot to mention I changed this to |
||
storage, | ||
) | ||
.unwrap(); | ||
}; | ||
|
||
let serialized = original_config.as_toml().unwrap(); | ||
|
||
let deserialized = AccountComponentMetadata::from_toml(&serialized).unwrap(); | ||
assert_eq!(deserialized, original_config) | ||
assert_eq!(deserialized, original_config); | ||
} | ||
|
||
#[test] | ||
fn test_new_non_contiguous_value_slots() { | ||
let storage = vec![ | ||
StorageEntry::Value { | ||
name: "slot0".into(), | ||
description: None, | ||
slot: 0, | ||
value: Default::default(), | ||
}, | ||
StorageEntry::Value { | ||
name: "slot2".into(), | ||
description: None, | ||
slot: 2, | ||
value: Default::default(), | ||
}, | ||
StorageEntry::new_value(0, default_felt_array()), | ||
StorageEntry::new_value(2, default_felt_array()), | ||
]; | ||
|
||
let result = AccountComponentMetadata::new( | ||
|
@@ -412,40 +396,31 @@ mod tests { | |
#[test] | ||
fn test_binary_serde_roundtrip() { | ||
let storage = vec![ | ||
StorageEntry::MultiSlot { | ||
name: "slot1".into(), | ||
description: None, | ||
slots: vec![1, 2], | ||
values: vec![ | ||
WordRepresentation::Array(Default::default()), | ||
WordRepresentation::Value(Default::default()), | ||
], | ||
}, | ||
StorageEntry::Value { | ||
name: "slot0".into(), | ||
description: None, | ||
slot: 0, | ||
value: WordRepresentation::Value(Default::default()), | ||
}, | ||
StorageEntry::new_multislot( | ||
StorageValueName::new("slot1").unwrap(), | ||
Option::<String>::None, | ||
vec![1, 2], | ||
vec![default_felt_array(), default_felt_array()], | ||
), | ||
StorageEntry::new_value(0, default_felt_array()), | ||
]; | ||
|
||
let component_template = AccountComponentMetadata::new( | ||
"test".into(), | ||
"desc".into(), | ||
Version::parse("0.1.0").unwrap(), | ||
BTreeSet::new(), | ||
let component_metadata = AccountComponentMetadata { | ||
name: "test".into(), | ||
description: "desc".into(), | ||
version: Version::parse("0.1.0").unwrap(), | ||
targets: BTreeSet::new(), | ||
storage, | ||
) | ||
.unwrap(); | ||
}; | ||
|
||
let library = Assembler::default().assemble_library([CODE]).unwrap(); | ||
let template = AccountComponentTemplate::new(component_template, library); | ||
_ = AccountComponent::from_template(&template, &InitStorageData::default()).unwrap(); | ||
let template = AccountComponentTemplate::new(component_metadata, library); | ||
let _ = AccountComponent::from_template(&template, &InitStorageData::default()).unwrap(); | ||
|
||
let serialized = template.to_bytes(); | ||
let deserialized = AccountComponentTemplate::read_from_bytes(&serialized).unwrap(); | ||
|
||
assert_eq!(deserialized, template) | ||
assert_eq!(deserialized, template); | ||
} | ||
|
||
#[test] | ||
|
@@ -461,8 +436,8 @@ mod tests { | |
description = "A storage map entry" | ||
slot = 0 | ||
values = [ | ||
{ key = "0x1", value = ["{{value.test}}", "0x1", "0x2", "0x3"] }, | ||
{ key = "0x1", value = ["0x1", "0x2", "0x3", "{{value.test}}"] }, | ||
{ key = "0x1", value = ["0x3", "0x1", "0x2", "0x3"] }, | ||
{ key = "0x1", value = ["0x1", "0x2", "0x3", "0x10"] } | ||
] | ||
"#; | ||
|
||
|
@@ -483,22 +458,21 @@ mod tests { | |
description = "A storage map entry" | ||
slot = 0 | ||
values = [ | ||
{ key = ["0","0","0","1"], value = ["{{value.test}}", "0x1", "0x2", "0x3"] }, | ||
{ key = "{{word.test}}", value = ["0x1", "0x2", "0x3", "{{value.test}}"] }, | ||
{ key = ["0", "0", "0", "1"], value = ["0x9", "0x12", "0x31", "0x18"] }, | ||
{ key = {name="duplicate_key" }, value = ["0x1", "0x2", "0x3", "0x4"] } | ||
] | ||
"#; | ||
|
||
let metadata = AccountComponentMetadata::from_toml(toml_text).unwrap(); | ||
let library = Assembler::default().assemble_library([CODE]).unwrap(); | ||
let template = AccountComponentTemplate::new(metadata, library); | ||
|
||
let init_storage_data = InitStorageData::new([ | ||
( | ||
StoragePlaceholder::new("word.test").unwrap(), | ||
StorageValue::Word([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ONE]), | ||
), | ||
(StoragePlaceholder::new("value.test").unwrap(), StorageValue::Felt(Felt::ONE)), | ||
]); | ||
// Fail to instantiate on a duplicate key | ||
|
||
let init_storage_data = InitStorageData::new([( | ||
StorageValueName::new("map.duplicate_key").unwrap(), | ||
"0x0000000000000000000000000000000000000000000000000100000000000000".to_string(), | ||
)]); | ||
let account_component = AccountComponent::from_template(&template, &init_storage_data); | ||
assert_matches!( | ||
account_component, | ||
|
@@ -507,13 +481,12 @@ mod tests { | |
)) | ||
); | ||
|
||
let valid_init_storage_data = InitStorageData::new([ | ||
( | ||
StoragePlaceholder::new("word.test").unwrap(), | ||
StorageValue::Word([Felt::new(30), Felt::new(20), Felt::new(10), Felt::ZERO]), | ||
), | ||
(StoragePlaceholder::new("value.test").unwrap(), StorageValue::Felt(Felt::ONE)), | ||
]); | ||
// Succesfully instantiate a map (keys are not duplicate) | ||
|
||
let valid_init_storage_data = InitStorageData::new([( | ||
StorageValueName::new("map.duplicate_key").unwrap(), | ||
"0x30".to_string(), | ||
)]); | ||
AccountComponent::from_template(&template, &valid_init_storage_data).unwrap(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This binding is not actually passed to
AccountComponentTemplate::new
, which seems to have been the intention?The test passes anyway, because the data in
InitStorageData
is just not used. I wonder if it should error if not all values from theInitStorageData
are used? But this may be somewhat complicated to track. As long as we don't have default values, then that should be fine, since a mistyped key would still produce an error and it would not simply fallback to the default.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I don't think it would be too complicated to track, so maybe it is worth implementing.