Skip to content
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

Open
wants to merge 11 commits into
base: next
Choose a base branch
from

Conversation

igamigo
Copy link
Collaborator

@igamigo igamigo commented Feb 19, 2025

Closes #1052

Comment on lines 198 to 209
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))
}
}
Copy link
Collaborator Author

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

Copy link
Contributor

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.

Comment on lines 65 to 67
/// An optional description for the slot, explaining its purpose.
description: Option<String>,
Copy link
Collaborator Author

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

Copy link
Contributor

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>,
Copy link
Collaborator Author

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)

Copy link
Contributor

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).

@igamigo igamigo force-pushed the igamigo-template-followups branch from fa017e8 to 1cf5186 Compare February 20, 2025 20:03
@igamigo igamigo marked this pull request as ready for review February 20, 2025 20:03
Copy link
Contributor

@bobbinth bobbinth left a 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.

Comment on lines 172 to 174
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
Copy link
Contributor

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.?

Copy link
Collaborator Author

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")
Copy link
Contributor

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 282 to 289
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(),
));
}
}
Copy link
Contributor

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated validations and explanations

Comment on lines 17 to 18
/// Creates a new instance of [InitStorageData].
/// A [`BTreeMap`] is constructed from the passed iterator, so duplicate keys will cause
Copy link
Contributor

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.

Comment on lines 45 to 46
#[cfg(test)]
mod tests {
Copy link
Contributor

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?

Copy link
Collaborator Author

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)

Comment on lines 65 to 67
/// An optional description for the slot, explaining its purpose.
description: Option<String>,
Copy link
Contributor

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>,
Copy link
Contributor

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).

Copy link
Contributor

@PhilippGackstatter PhilippGackstatter left a 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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.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}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably leftover?

Comment on lines 209 to 217
/// 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")
}
Copy link
Contributor

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.

Copy link
Collaborator Author

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.

Comment on lines 302 to 304
let native: u16 = input
.parse()
.map_err(|_| TemplateTypeError::ParseError(input.to_string(), Self::type_name()))?;
Copy link
Contributor

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:

/// 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"
Copy link
Contributor

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.

Comment on lines +379 to +382
let start = slots[0];
let end = slots.last().expect("checked validity") + 1;

Ok(StorageEntry::new_multislot(name, raw.description, start..end, values))
Copy link
Contributor

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.

Copy link
Collaborator Author

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

Copy link
Contributor

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)?;
Copy link
Contributor

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).

Copy link
Collaborator Author

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()

Comment on lines 461 to 462
#[error("failed to parse TOML: {0}")]
InvalidToml(#[from] toml::de::Error),
Copy link
Contributor

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}.

#[error("error deserializing component metadata: {0}")]
MetadataDeserializationError(String),
#[error("multi-slot entry should contain as many values as storage slots indices")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[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")]

Comment on lines 121 to 125
/// let last_element = FeltRepresentation::new_template(
/// TemplateType::new("felt")?,
/// StorageValueName::new("foo")?,
/// None,
/// );
Copy link
Contributor

@PhilippGackstatter PhilippGackstatter Feb 25, 2025

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 .

Copy link
Collaborator Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants