refactor: Implement component schemas#2193
Conversation
bobbinth
left a comment
There was a problem hiding this comment.
Looks great! Thank you! Not a full review from me, but I left some comments inline.
PhilippGackstatter
left a comment
There was a problem hiding this comment.
Not a full review as I did not get through everything. The overall structure of what I've reviewed makes sense to me, though.
crates/miden-objects/src/account/component/storage/toml/init_storage_data.rs
Show resolved
Hide resolved
| } else { | ||
| return Err(InitStorageDataError::ArraysNotSupported); | ||
| } |
There was a problem hiding this comment.
Nit: We run into this when a user specifies an array of size 5, for instance, right? It would be nice to provide more context here to help with finding the error. I guess an alternative to including the actual size here is to mention the allowed layout in the error message.
| pub static SCHEMA_TYPE_REGISTRY: LazyLock<SchemaTypeRegistry> = LazyLock::new(|| { | ||
| let mut registry = SchemaTypeRegistry::new(); | ||
| registry.register_felt_type::<Void>(); | ||
| registry.register_felt_type::<u8>(); | ||
| registry.register_felt_type::<u16>(); | ||
| registry.register_felt_type::<u32>(); | ||
| registry.register_felt_type::<Felt>(); | ||
| registry.register_felt_type::<TokenSymbol>(); | ||
| registry.register_word_type::<Word>(); | ||
| registry.register_word_type::<rpo_falcon512::PublicKey>(); | ||
| registry.register_word_type::<ecdsa_k256_keccak::PublicKey>(); | ||
| registry | ||
| }); |
There was a problem hiding this comment.
This works for now, but after #2191, I think we should move TokenSymbol to miden-standards.
Similarly, registering rpo_falcon512::PublicKey and ecdsa_k256_keccak::PublicKey here also feels like it should be done at the standards level, because, afaict, these types are useful only in the context of their respective auth components, which are standards.
Just mentioning this in case we don't have an issue for making the type registry user-extensible, but nothing to do for this PR I think.
There was a problem hiding this comment.
Yeah, I don't think we have a specific issue for making the registry user-extensible (was briefly mentioned in #2062 (comment)), but I agree we should keep it in mind
There was a problem hiding this comment.
Yes, we should create a separate issue for making the registry extensible.
crates/miden-objects/src/account/component/metadata/storage/type_registry.rs
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| /// Trait for converting a string into a single `Word`. | ||
| pub trait WordType: alloc::fmt::Debug + Send + Sync { |
There was a problem hiding this comment.
Nit: Why does this trait require Debug but FeltType does not? Should they both require it?
There was a problem hiding this comment.
I've removed this. I tried different approaches to enable displaying types (for re-serialization, but also for enabling displaying values in places like the explorer, etc.) and I think this was a leftover from one of those attempts
| Felt, | ||
| } | ||
|
|
||
| fn word_type_kind(schema_type: &SchemaTypeIdentifier) -> WordTypeKind { |
There was a problem hiding this comment.
Nit: Could this function be a method on SchemaTypeIdentifier instead?
There was a problem hiding this comment.
I'm thinking this is more type registry-related. If the registry is somehow user-extensible, it will become the source of truth for identifying types and what they map to in terms of converting to native types. I'll note this in the list of followups
| } | ||
| } | ||
|
|
||
| fn validate_word_value( |
There was a problem hiding this comment.
Nit: Similarly, could this and validate_felt_value be methods on SchemaTypeIdentifier? Just to avoid free functions that have less context.
mmagician
left a comment
There was a problem hiding this comment.
I think the overall structure makes a lot of sense. Also thank you for extensive tests, these showcase the new functionality really well.
crates/miden-objects/src/account/component/storage/toml/tests.rs
Outdated
Show resolved
Hide resolved
| type = [ | ||
| { type = "u32", name = "max_supply", description = "Maximum supply (base units)" }, | ||
| { type = "token_symbol", name = "symbol", default-value = "TST" }, | ||
| { type = "u8", name = "decimals", description = "Token decimals" }, | ||
| { type = "void" } | ||
| ] |
There was a problem hiding this comment.
What happens if type array contains less than four entries?
There was a problem hiding this comment.
It will error as it expects exactly 4. As you suggested in the other comment, we could make void the default type and have it fill the remaining elements.
| /// Returns the init-time values required to instantiate this schema. | ||
| /// |
There was a problem hiding this comment.
| /// Returns the init-time values required to instantiate this schema. | |
| /// | |
| /// Returns the init-time values' requirements for this schema. | |
| /// |
| /// # Guarantees | ||
| /// | ||
| /// - The metadata's storage schema does not contain duplicate slot names. | ||
| /// - The schema cannot contain protocol-reserved slot names. |
There was a problem hiding this comment.
not for this PR, but currently protocol-reserved slot names are hardcoded to a single check:
if component_slot.name() == Self::faucet_sysdata_slot() {
return Err(AccountError::StorageSlotNameMustNotBeFaucetSysdata);
}One idea would be to encode this in some helper enum ReservedSlotNames (or similar), which we could then reference from doc strings like this one here. On the other hand, this sounds like a bit of an overkill for the single reserved slot name that we have. But I admit, it's a little hard to find what protocol reserved slots are in the codebase.
There was a problem hiding this comment.
Generally agree this would be nice, but I think we want to get rid of the faucet sysdata slot and make issuance tracking the responsibility of the faucet implementation, in which case we'd no longer have any protocol-reserved slots at all, which would be even better.
There was a problem hiding this comment.
Agreed - though for now we could probably go with the RESERVED_SLOT_NAMES as already implemented in #2207
It will be equally easy to change if/once we remove the faucet's reserved slot.
docs/src/account/components.md
Outdated
| - **Singular**: defined through the `type` field, indicating the expected `SchemaTypeIdentifier` for the entire word. The value is supplied at instantiation time via `InitStorageData`. | ||
| - **Composed**: provided through `type = [ ... ]`, which contains exactly four `FeltSchema` descriptors. Each element is either a named typed field (optionally with `default-value`) or a `void` element for reserved/padding zeros. |
There was a problem hiding this comment.
which category does this fall into?
[[storage.value]]
name = "demo::protocol_version"
description = "A whole-word init-supplied value typed as a felt (stored as [0,0,0,<value>])."
type = "u8"I think this is equivalent to an inferred-composite schema: [u8, void, void, void], so it might be somewhat confusing.
…rsing to use registry
|
Thanks for all the reviews! I think I got most of the comments. I explicitly left some for followups (some described in the PR description, some in the comments of your suggestions). |
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! Not a super thorough review from me, but I left some comments inline (and there are a few un-addressed comments from previous reviews). Once these are addressed, we should be good to merge.
I think there are two broad categories of issues that we can push into the follow-ups:
- Refactoring of how
InitStorageDataworks (this should be relatively straight-forward). - Making the type registry extensible (this will require some discussion).
crates/miden-objects/src/account/component/storage/init_storage_data.rs
Outdated
Show resolved
Hide resolved
crates/miden-objects/src/account/component/storage/init_storage_data.rs
Outdated
Show resolved
Hide resolved
crates/miden-objects/src/account/component/storage/type_registry.rs
Outdated
Show resolved
Hide resolved
crates/miden-objects/src/account/component/storage/type_registry.rs
Outdated
Show resolved
Hide resolved
mmagician
left a comment
There was a problem hiding this comment.
I still prefer explicit [[storage.{map/value}]] over [[storage.slots]] for clarity when defining the TOML, but I don't want to get bogged down on this. LGTM with one small formatting nit
crates/miden-objects/src/account/component/storage/toml/tests.rs
Outdated
Show resolved
Hide resolved
crates/miden-objects/src/account/component/storage/toml/tests.rs
Outdated
Show resolved
Hide resolved
| ```toml | ||
| [[storage.slots]] | ||
| name = "demo::faucet_id" | ||
| description = "Account ID of the registered faucet" | ||
| type = [ | ||
| { type = "felt", name = "prefix", description = "Faucet ID prefix" }, | ||
| { type = "felt", name = "suffix", description = "Faucet ID suffix" }, | ||
| { type = "void" }, | ||
| { type = "void" }, | ||
| ] | ||
| ``` |
There was a problem hiding this comment.
I think this belongs somewhere in the "Storage entries" section.
|
Client PR: 0xMiden/miden-client#1626 |
* feat: renames, doc improvements, submodule export * Update crates/miden-protocol/src/account/component/storage/type_registry.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Marti <marti@miden.team> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* refactor: replace templates with component schemas * chore: overrideable -> overridable * chore: more docs * feat: avoid overloading type word and define slot type with the dotted key * chore: CHANGELOG * chore: update docs * chore: update docs * chore: display * chore: spellcheck * reviews: address most of the first review's smaller comments * reviews: infer type based o type structure * reviews: re-enable tests * reviews: docs, typeregstry renames, doc comment rewrites * chore: lints * reviews: give context to errors, simplify validations, revert felt parsing to use registry * reviews: simplify further * reviews: initvaluerequirements -> schemarequirements; now collected into map * reviews: more doc suggestions applied; validate schema * reviews: singular->simple, scalar->atomic, docs reviews, nits * reviews: initstoragedata duplicate detection * feat: scope storagevaluename * chore: docs fix * chore: fix indentaion --------- Co-authored-by: Marti <marti@miden.team>
* feat: renames, doc improvements, submodule export * Update crates/miden-protocol/src/account/component/storage/type_registry.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Marti <marti@miden.team> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* feat: renames, doc improvements, submodule export * Update crates/miden-protocol/src/account/component/storage/type_registry.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Marti <marti@miden.team> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Closes #2062.
This is mostly a rewrite of the account component templating structures. The main refactor implies moving away from templates and allowing users to describe component schemas, where a schema just represents the types of values that a storage slot is supposed to allow, with optional default (but overridable at instantiation) values for each of those slots.
Most of the concepts and structures used to serialize and deserialize the schema in this PR are similar to what we had before. However, because this also changes nomenclature of a lot of structures, variables, etc., even similar code will appear new in the diff. Additionally, I moved some code around into their own submodules for readability.
For reviewing this I first recommend going through:
extensive_schema_metadata_and_init_toml_exampletest which tests most of the related features in their happy pathsNotable changes
word,auth::ecdsa_k256_keccak::pub_keyas we had before), or as a product/tuple type of four typed felts.wordis still the generic default type of a value slot andfeltis the generic default element type.fungible_faucets::metadata::token_symboloru8) can now be used to type a word as well (where the word is stored as[0, 0, 0, <felt_type_value>]).voidtype was introduced for felts to express elements that are not part of the product type. For example, on a metadata storage slot there may be a padding element:[u32, token_symbol, u8, void].voidelements must omitnameanddefault-valueand always resolve to0.slot_name.field_name. For instance, if the slot ismiden::standards::fungible_faucets::metadata, you can reference thedecimalselement viamiden::standards::fungible_faucets::metadata.decimals.[[storage.slot]], and the slot kind is inferred by the shape of the requiredtypefield:type = "..."ortype = [ ... ]describes a value slot.type = { key = ..., value = ... }(or equivalentlytype.key = ...andtype.value = ...) describes a map slot. I believe this is an overall positive change, because it avoids overloadingtype = "map"to define that we are talking about a storage map, while also usingtype = "..."to define that this was a value slot with a specific data type. It also makes (de)serialization simpler because structs map more cleanly to their TOML representations. But since this was not discussed, it was done on a separate commit to easily undo it if we don't like it.::must be written in quotes (e.g."demo::token_metadata.max_supply" = "1000000"). This is a TOML grammar limitation. Additionally, init values are currently required to be TOML strings (including numeric values), and are parsed/ validated against the schema at instantiation time.**Rundown of related structs**
AccountComponentMetadata: similar to previous approach, top-level “schema metadata” (name/description/version/supported account types) plus the storage schemaAccountStorageSchema: a set of named slots for the component; builds concrete storage slots fromInitStorageDataStorageSlotSchema: per-slot schema, either a single-word value slot or a map slotValueSlotSchema: describes a single word; delegatestyping toWordSchemaWordSchema::Singular: the whole word is one typed value supplied at init time (optionally with an overridable default)WordSchema::Composed: fixed 4-felt layout where each felt has its ownFeltSchema(typed field, default, or void padding)FeltSchema: describes one felt inside a composed word; non-void felts must be named (so they can be provided/overridden); void is always zero paddingMapSlotSchema: describes a map slot, can have staticdefault_valuesand optional key/value schemas; init-provided entries are optional (omitting them gets you an empty map unless defaults exist).StorageValueName: similar as before; the key space for init values; typically derived from a slot name, with optional.fieldsuffixes for composed-word typed fieldsInitStorageDatawithWordValue: also similar as before; raw init-time inputs before parsing/validation against the schema and type registrySchemaTypeRegistry: Similar to what we had before withTemplateRegistryFollow-ups/open questions:
I have a rework of
InitStorageDatathat tackles ImproveInitStorageDatastructure for better usability #1860 by giving it a constructor that works with native types instead of a string pretty much ready, will open a separate PR for itStorageValueNameshould also be reworked. With this PR, when usingInitStorageDatayou can either reference a slot directly via itsStorageSlotName's string identifier, or an element of the storage slot by suffixing it with a.elementif the storage slot is of a tuple type. So, we can typeStorageValueNameto follow these directives instead of it being mostly based onStrings.Tackling Account storage schema validation #2104:
AccountStorageSchema, but this also currently includes default values. Three different approaches here:AccountStorageSchemaand put them at the level ofAccountComponentMetadata. When parsing the metadata TOML we can just grab all defaults and put them in a separate mapping, instead of coupling them with the schema itself.