-
Notifications
You must be signed in to change notification settings - Fork 60
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: TemplateType
newtype, docs, tests, InitStorageData::from_toml
#1170
base: next
Are you sure you want to change the base?
Conversation
impl Serializable for TemplateType { | ||
fn write_into<W: ByteWriter>(&self, target: &mut W) { | ||
target.write(self.0.clone()) | ||
} | ||
} | ||
|
||
impl Deserializable for TemplateType { | ||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> { | ||
let id: String = source.read()?; | ||
Ok(TemplateType::new(id)) | ||
} | ||
} |
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.
Unfortunately could not make TemplateType
contain an inner 'static &str
due to serialization, unless we wanted deserialization to leak memory
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.
I guess it would be possible with an inner representation of Cow<'static, str>
? But not essential I think.
/// An optional description for the slot, explaining its purpose. | ||
description: Option<String>, |
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.
name
and description
could be made into their own type since they always appear together, would slightly simplify serde code
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.
Would this be something like?:
pub struct StorageEntryIdent {
name: StorageValueName,
description: Option<String>,
}
I think this could be a good idea, but I'd probably do this in a separate PR.
@@ -64,7 +65,7 @@ pub enum StorageEntry { | |||
/// An optional description for the slot, explaining its purpose. | |||
description: Option<String>, | |||
/// The indices of the slots that form this multi-slot entry. | |||
slots: Vec<u8>, | |||
slots: Range<u8>, |
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.
One other change that should be made for Multislot
entries is to factor out the actual data into a MultislotRepresentation
as done with the other two variants. This would make StorageEntry
variants more streamlined (but also we would need a MultislotRepresentation::Template
for when templating in multislot values is enabled)
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.
Similar to the above comment: seems like a good idea but I'd do it in a follow-up PR (let's create an issue for follow-ups).
fa017e8
to
1cf5186
Compare
…nto igamigo-template-followups
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.
Looks good! Thank you! Not a very deep review, but I left some small comments inline.
pub fn new(s: impl Into<String>) -> Self { | ||
Self(s.into()) | ||
} |
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.
Do we want to impose any restrictions on what characters are allowed etc.?
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.
Added some validations
fn type_name() -> &'static str { | ||
"word" | ||
fn type_name() -> TemplateType { | ||
TemplateType::new("word") |
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.
Should we use default_word_type()
here?
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.
Done
if let Some(name) = entry.name() { | ||
let name_existed = !seen_names.insert(name.as_str()); | ||
if name_existed { | ||
return Err(AccountComponentTemplateError::DuplicateEntryNames( | ||
name.as_str().into(), | ||
)); | ||
} | ||
} |
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.
We should mention this error condition in the doc comments for this function.
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.
Updated validations and explanations
/// Creates a new instance of [InitStorageData]. | ||
/// A [`BTreeMap`] is constructed from the passed iterator, so duplicate keys will cause |
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.
nit: I'd add a newline between these lines.
#[cfg(test)] | ||
mod tests { |
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.
Should this test actually go into the toml
module? Originally, I was trying to find them there since that's where the implementation of the function they are testing is in.
Also, these tests should be done only in the std
context, right?
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.
Moved this to the toml
module (which is based on std
)
/// An optional description for the slot, explaining its purpose. | ||
description: Option<String>, |
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.
Would this be something like?:
pub struct StorageEntryIdent {
name: StorageValueName,
description: Option<String>,
}
I think this could be a good idea, but I'd probably do this in a separate PR.
@@ -64,7 +65,7 @@ pub enum StorageEntry { | |||
/// An optional description for the slot, explaining its purpose. | |||
description: Option<String>, | |||
/// The indices of the slots that form this multi-slot entry. | |||
slots: Vec<u8>, | |||
slots: Range<u8>, |
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.
Similar to the above comment: seems like a good idea but I'd do it in a follow-up PR (let's create an issue for follow-ups).
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.
Looks good to me!
I left some comments inline. The main question is whether we allow and want to allow multi slot entries of size 1.
storage_placeholders: entries.into_iter().collect(), | ||
storage_placeholders: entries | ||
.into_iter() | ||
.filter(|(k, _)| !k.as_str().is_empty()) |
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.
.filter(|(k, _)| !k.as_str().is_empty()) | |
.filter(|(entry_name, _)| !entry_name.as_str().is_empty()) |
Nit: Readability.
@@ -434,13 +430,14 @@ mod tests { | |||
storage, | |||
}; | |||
let toml = config.as_toml().unwrap(); | |||
println!("{toml}"); |
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.
Probably leftover?
/// Returns the default felt type, assigned to entries with no type. | ||
pub fn default_felt_type() -> TemplateType { | ||
TemplateType::new("felt").expect("type is well formed") | ||
} | ||
|
||
/// Returns the default word type, assigned to entries with no type. | ||
pub fn default_word_type() -> TemplateType { | ||
TemplateType::new("word").expect("type is well formed") | ||
} |
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.
Could we name these TemplateType::native_felt()
and TemplateType::native_word()
or would that be ambiguous somehow? I feel like that would better capture that these are the base types, rather than just some 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.
Yeah. The *_type
suffix is redundant because these are now type functions, and the default_*
prefix, while descriptive of their use within the templates, is probably not too appropriate as part of type functions either, so I'll resort to your alternative.
let native: u16 = input | ||
.parse() | ||
.map_err(|_| TemplateTypeError::ParseError(input.to_string(), Self::type_name()))?; |
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.
It would be nice if we could include the source errors in the ParseError
. Should we add a source: Box<dyn Error + Send + Sync + 'static>
field maybe?
It seems like this error variant would also be used by user implementations (eventually) and they could have more complex source errors which would be great to include to help with debugging.
We could add convenience constructors similar to these:
miden-base/crates/miden-tx/src/errors/mod.rs
Lines 62 to 80 in c5d6275
/// Creates a custom error using the [`TransactionProverError::Other`] variant from an error | |
/// message. | |
pub fn other(message: impl Into<String>) -> Self { | |
let message: String = message.into(); | |
Self::Other { error_msg: message.into(), source: None } | |
} | |
/// Creates a custom error using the [`TransactionProverError::Other`] variant from an error | |
/// message and a source error. | |
pub fn other_with_source( | |
message: impl Into<String>, | |
source: impl Error + Send + Sync + 'static, | |
) -> Self { | |
let message: String = message.into(); | |
Self::Other { | |
error_msg: message.into(), | |
source: Some(Box::new(source)), | |
} | |
} |
for pair in slots.windows(2) { | ||
if pair[1] != pair[0] + 1 { | ||
return Err(serde::de::Error::custom(format!( | ||
"`slots` field in `{name}` entry is not a valid range" |
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.
Wouldn't it be more accurate to say that the entries are not contiguous? 0, 2
would be a valid range in the 0..2
sense, but wouldn't be contiguous in the 0, 2
sense.
let start = slots[0]; | ||
let end = slots.last().expect("checked validity") + 1; | ||
|
||
Ok(StorageEntry::new_multislot(name, raw.description, start..end, values)) |
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.
I think we currently allow multi slot entries of size 1. Should we require somewhere (here?) that at least two entries exist? If that's the case, I guess a follow-up PR makes the most sense.
I tried parsing this into an AccountComponentMetadata
and it worked:
name = "Test Component"
description = "Test multislot non contiguous"
version = "1.0.1"
supported-types = ["FungibleFaucet"]
[[storage]]
name = "multislot_non_contiguous"
slots = [0]
values = [
[ "0x1", "0x2", "0x3", "0x4" ],
]
Is that intentional? I'm not saying this is wrong, but if this is allowed, there is some overlap between this and a regular non-multi entry and we may want to support just one way of doing this instead of two, to reduce the "API surface" of these features and what we have to maintain long-term.
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.
Yeah, I guess I was considering these unfinished until we implemented types, etc. but this makes sense to add right now
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.
Ah I see, feel free to do in a follow-up PR then.
let value: toml::Value = toml::from_str(toml_str)?; | ||
let mut placeholders = BTreeMap::new(); | ||
// Start with an empty prefix (i.e. the default, which is an empty string) | ||
Self::flatten_parse_toml_value(StorageValueName::default(), &value, &mut placeholders)?; |
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.
Nit: For readability, maybe it would be clearer if we would use StorageValueName::empty()
instead of default()
? (Still fine to have 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.
Agreed. I'll also remove default()
#[error("failed to parse TOML: {0}")] | ||
InvalidToml(#[from] toml::de::Error), |
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.
#[from]
implies #[source]
so this both includes the error in the display impl as well as return it from source. It should only do one of those two. For simplicity, you could just remove the : {0}
.
crates/miden-objects/src/errors.rs
Outdated
#[error("error deserializing component metadata: {0}")] | ||
MetadataDeserializationError(String), | ||
#[error("multi-slot entry should contain as many values as storage slots indices")] |
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.
#[error("multi-slot entry should contain as many values as storage slots indices")] | |
#[error("multi-slot entry should contain as many values as storage slot indices")] |
/// let last_element = FeltRepresentation::new_template( | ||
/// TemplateType::new("felt")?, | ||
/// StorageValueName::new("foo")?, | ||
/// None, | ||
/// ); |
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.
I wonder if it would be a nicer API if new_{template, value}
would not take the optional description and we instead had a with_description(mut self, description: impl Into<String>) -> Self
method? Then these could be chained together nicely to set the description .
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.
In terms of API I agree, although it will make parsing a little bit more verbose. Probably a net win though.
…ce; constructors/propery-setters
…nto igamigo-template-followups
Closes #1052