-
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 AccountComponentTemplate
#1015
Conversation
0cee24f
to
123e92c
Compare
ComponentPackage
and ComponentConfig
f12c492
to
1f66753
Compare
ComponentPackage
and ComponentConfig
ComponentPackage
and ComponentTemplate
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! I left just a few comments inline.
266af8f
to
34abed2
Compare
e5291d0
to
9df6689
Compare
if slots.len() != values.len() { | ||
return Err(ComponentMetadataError::InvalidMultiSlotEntry); | ||
} | ||
|
||
Ok(StorageEntry::MultiSlot { | ||
name: name.into(), | ||
description: description.map(Into::<String>::into), | ||
slots, | ||
values: values.into_iter().map(Into::into).collect(), | ||
}) |
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.
The description above says:
- A multi-slot entry (spanning multiple contiguous slots, with multipe values).
Should we check that the slots are contiguous? (I know we also check it at the ComponentPackage
level.
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 this change
if !slot_present { | ||
return Err(D::Error::missing_field("slot")); | ||
} | ||
if raw.value.is_some() { |
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: could we use value_present
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.
Agreed!
if slot_present { | ||
return Err(D::Error::custom( | ||
"Fields 'slot' and 'slots' are mutually exclusive.", | ||
)); | ||
} | ||
if value_present { | ||
return Err(D::Error::custom( | ||
"Fields 'value' and 'values' are mutually exclusive.", | ||
)); | ||
} |
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.
These checks for mutual exclusivity can be done before the match to reduce code repetition
slot: raw.slot.expect("was checked to be present"), | ||
value: raw.value.expect("was checked to be present"), |
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.
instead of the if + expect we could do .ok_or(...)?
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.
Changed! I ended up refactoring this section of code many times and left a mix of solutions, but these changes are helping shape it up better
let slots = raw.slots.ok_or_else(|| D::Error::missing_field("slots"))?; | ||
let values = raw.values.ok_or_else(|| D::Error::missing_field("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.
If we change the match to be match (raw.slots, raw.values)
we could deconstruct in the case to (Some(slots), Some(values))
and remove these ok_or_else
.
impl Default for WordRepresentation { | ||
fn default() -> Self { | ||
WordRepresentation::SingleHex(Default::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.
Should this be in the previous section?
} | ||
// Enforce and skip the '0x' prefix. | ||
let hex_bytes = match hex.as_bytes() { | ||
[b'0', b'x', rest @ ..] => rest, |
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 allow for "0X" prefix like we do for Felts?
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 I will disallow 0X
in felts instead, unless there's a good reason to do otherwise. cc @bobbinth @PhilippGackstatter (and probably @Fumuran) do you think 0X
should be a valid prefixes for things like the digest!()
macro?
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.
If it doesn't complicate things too much, i think making 0x
prefix optional could be a good idea. For example, the following would result in an identical digest:
digest!("123456");
digest!("0x123456");
But this is a pretty minor thing - so, I'm fine with keeping as is as well.
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.
digest!("123456"); digest!("0x123456");
Not sure I'm following the context completely, but isn't it confusing to have these two be the same? Unless you know that digest!
takes a hex string, it would be easy to interpet digest!("123456");
as a decimal number, especially since macros are even less predictable than regular Rust functions. So wouldn't it be better to enforce hex strings to start with 0x
to avoid ambiguity?
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 - good point! Scratch my comment then - let's enforce 0x
prefix for hex values.
// SAFETY: u8 cast to u64 is safe. We cannot use u64::from in const context so we | ||
// are forced to cast. |
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 add this safety comment back to the match case
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 catch, re-added
ComponentPackage
and ComponentTemplate
AccountComponentTemplate
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 conceptually!
There are quite a few pub types that are not documented which would be great to add.
I'm also wondering if making everything pub
by default is good. There are a lot of new types added in this PR and the API surface of miden-base increases quite a bit. If we only export some of the higher level types, it would be great to double-check if the lower level types are properly excluded from the docs and that they are not exposed through methods on the higher level types (although rustdoc should complain in that case anyway).
Otherwise, I left many small nits, mostly on errors and docs. Feel free to pick what you think makes sense!
objects/src/accounts/package/mod.rs
Outdated
mod storage_entry; | ||
pub use storage_entry::{StorageEntry, TemplateKey, TemplateValue}; | ||
|
||
// COMPONENT PACKAGE |
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.
// COMPONENT PACKAGE | |
// ACCOUNT COMPONENT TEMPLATE |
I guess the file name or module name should also be template then?
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.
Changed this
/// they are not of a valid type) | ||
#[cfg(feature = "std")] | ||
pub fn from_template( | ||
package: &AccountComponentTemplate, |
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.
package: &AccountComponentTemplate, | |
template: &AccountComponentTemplate, |
Nit: Looks like this should be template
or something else instead of package
now.
The documentation might also need an update to reflect the new naming.
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.
Thanks, changed this
objects/src/accounts/package/mod.rs
Outdated
/// a component within the system. It includes the configuration details and the compiled | ||
/// library code required for the component's operation. | ||
/// | ||
/// A package can be instantiated into [AccountComponent](super::AccountComponent) objects. |
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.
/// A package can be instantiated into [AccountComponent](super::AccountComponent) objects. | |
/// A template can be instantiated into [AccountComponent](super::AccountComponent) objects. |
objects/src/accounts/package/mod.rs
Outdated
/// The component's metadata. This describes the component and how the storage is laid out, | ||
/// alongside how storage values are initialized. | ||
metadata: ComponentMetadata, | ||
/// The account's previously-assembled code. This defines all functionality related to the |
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.
/// The account's previously-assembled code. This defines all functionality related to the | |
/// The account component's assembled code. This defines all functionality related to the |
Nit: Maybe just this?
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.
Sounds good! Changed
objects/src/accounts/package/mod.rs
Outdated
/// The component metadata can be defined with generic keys that can be replaced at instantiation | ||
/// time. |
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: Here it's called "generic keys" while in other places it seems to be called "template keys". Ideally, we use just one term for this.
Reading this for the first time, my immediate question is how these template keys work, so it would be great to link to some place where more can be learned, or even add a more extensive example here (or as module docs).
|
||
impl core::fmt::Display for TemplateKey { | ||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
write!(f, "{}", self.key) |
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'm wondering if this should include braces. The inverse operation is basically TryFrom<&str>
and it would expect braces, so maybe this would be most consistent.
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.
Yes, agreed, good catch. I originally did From<&str>
instead of TryFrom<&str>
which worked as the inverse of this, but then changed it based on Bobbin's suggestion and didn't realize this should've been changed as well.
WordRepresentation::Array(array) => { | ||
let mut result = [Felt::ZERO; 4]; | ||
for (index, felt_repr) in array.iter().enumerate() { | ||
result[index] = felt_repr.clone().try_into_felt(template_values)?; | ||
} | ||
Ok(result) | ||
}, |
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 be good to add a comment here that every index of result
is guaranteed to be overwritten.
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 this
} else if let Ok(decimal_value) = value.parse::<u64>() { | ||
Ok(FeltRepresentation::SingleDecimal(Felt::new(decimal_value))) |
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 don't think we would want to silently wrap around if the given u64
exceeds the field modulus. If users specify such a value it would always be an error I would think, so it would be better to use Felt::try_from
and propagate the 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.
Good catch, changed
} else if let Ok(key) = TemplateKey::try_from(&value) { | ||
Ok(FeltRepresentation::Dynamic(key)) | ||
} else { | ||
Err(serde::de::Error::custom("Value is not a valid element")) |
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.
Err(serde::de::Error::custom("Value is not a valid element")) | |
Err(serde::de::Error::custom("deserialized string value is not a valid variant of FeltRepresentation")) |
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.
Changed this
Yeah, I left a |
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.
Not a review - but quickly scanning through the code, I think there are still some smaller comments that need to be addressed. And we also still need to introduce something like InitStorageData
struct.
Also, in doc comments we should update references to "package" vs. "template" (e.g., doc comments for AccountComponentTemplate
struct).
I think all comments have been addressed (but will double check just in case). The only remaining question from my side is whether we want |
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.
Thank you! Looks good! I left some comments inline.
One other thing I was thinking about is naming of TemplateKey
and TemplateValue
. Maybe these are fine, but I also thought that maybe these should indicate that we are dealing specifically with storage. An alternative could be something like StoragePlaceholder
and StorageValue
. But this is not a strong opinion by any means.
/// Returns an iterator over all of the storage entries's template keys. | ||
// TODO: Should template keys be typed? | ||
pub fn template_keys(&self) -> Box<dyn Iterator<Item = &TemplateKey> + '_> { |
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.
re TODO
: do you mean to get rid of dyn
? If so, I think having dyn
here is probably fine.
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.
What I meant is this, basically
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.
Let's create an issue for this. it sounds like a good idea, but I'm not sure this will be possible in all circumstances.
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.
Created #1051
I also changed |
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! I left a few comments inline - these are mostly nits or things we can do in subsequent PRs.
objects/src/accounts/component/template/storage/init_storage_data.rs
Outdated
Show resolved
Hide resolved
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! Just a couple of nits.
objects/src/accounts/component/template/storage/init_storage_data.rs
Outdated
Show resolved
Hide resolved
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.
All looks good! Thank you!
Part of #550.
Introduces a first draft of
AccountComponentPackage
andAccountComponentTemplate
.For now it can serialize and deserialize configs such as this one:
Most structs down to the element need to have custom (de)serialization due to the different alternatives discussed in #550.