Skip to content

Conversation

@Larkooo
Copy link
Collaborator

@Larkooo Larkooo commented Sep 25, 2025

Summary by CodeRabbit

  • New Features
    • Added an option to choose legacy vs new storage formats during serialization.
    • Serialization now consistently respects the chosen format across all schema types, including nested structures.
    • Enum handling and uninitialized values behave differently depending on format (new storage yields explicit zero markers; legacy preserves prior error/placeholder behavior).
    • Improved consistency and predictability when switching formats within complex schemas.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

ohayo, sensei

Walkthrough

Serialization APIs now accept a legacy_storage: bool flag. Member::serialize and Ty::serialize signatures changed to receive and forward the flag into serialize_inner, which threads legacy_storage through recursive serialization and adjusts enum/composite handling and error/zero semantics.

Changes

Cohort / File(s) Summary of Changes
Serialization API & logic
crates/dojo/types/src/schema.rs
- Member::serialize(&self)Member::serialize(&self, legacy_storage: bool) and forwards flag to Ty::serialize.
- Ty::serialize(&self)Ty::serialize(&self, legacy_storage: bool) and delegates to serialize_inner.
- fn serialize_inner(ty: &Ty, felts: &mut Vec<Felt>)fn serialize_inner(ty: &Ty, felts: &mut Vec<Felt>, legacy_storage: bool); flag is threaded through recursive calls for Struct/Tuple/Array/FixedSizeArray.
- Enum handling adjusted: behavior differs when legacy_storage is true vs false (option/selected-option layout, index offsets, uninitialized enum zero vs MissingFieldElement error).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Caller
    participant Member
    participant Ty
    participant Serializer as serialize_inner

    Caller->>Member: serialize(legacy_storage)
    Member->>Ty: serialize(legacy_storage)
    Ty->>Serializer: serialize_inner(self, felts, legacy_storage)
    loop recurse through nested Ty variants
        Serializer->>Serializer: descend(..., legacy_storage)
    end
    Serializer-->>Ty: Result<Vec<Felt>>
    Ty-->>Member: Result<Vec<Felt>>
    Member-->>Caller: Result<Vec<Felt>>

    note right of Serializer: legacy_storage toggles enum/index handling and error-vs-zero behavior
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the main change of adding support for both legacy and non-legacy serialization in the schema module by referencing the core feature and its scope, and it aligns with conventional commit style while remaining focused on the primary intent of the diff.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3e3aef and 779125d.

📒 Files selected for processing (1)
  • crates/dojo/types/src/schema.rs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
crates/dojo/types/src/schema.rs (1)
crates/dojo/types/src/primitive.rs (1)
  • serialize (769-831)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: fmt

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 779125d and 00f214d.

📒 Files selected for processing (1)
  • crates/dojo/types/src/schema.rs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
crates/dojo/types/src/schema.rs (1)
crates/dojo/types/src/primitive.rs (1)
  • serialize (769-831)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: fmt

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00f214d and f80751a.

📒 Files selected for processing (1)
  • crates/dojo/types/src/schema.rs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
crates/dojo/types/src/schema.rs (1)
crates/dojo/types/src/primitive.rs (1)
  • serialize (769-831)

Comment on lines +24 to 26
pub fn serialize(&self, legacy_storage: bool) -> Result<Vec<Felt>, PrimitiveError> {
self.ty.serialize(legacy_storage)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Respect key semantics in Member::serialize

ohayo sensei, Member::serialize now exposes the legacy_storage flag but ignores self.key. Callers that pass legacy_storage = false when serializing key members will emit the new 1-based encoding, while deserialize on Line 231 still forces legacy semantics via child.key || legacy_storage, breaking round-trips for key enums/structs. Please mirror that predicate here.

         self.ty.serialize(legacy_storage)
+        self.ty.serialize(self.key || legacy_storage)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In crates/dojo/types/src/schema.rs around lines 24 to 26, Member::serialize
currently ignores self.key and always passes legacy_storage through to
ty.serialize; update it to respect key semantics by calling
self.ty.serialize(self.key || legacy_storage) so key members force legacy
encoding and preserve round-trip behavior with deserialize.

Copy link
Collaborator

@glihm glihm left a comment

Choose a reason for hiding this comment

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

Thanks for the fix @Larkooo I'll let @remybar confirming. 👍

serialize_inner(&selected_option.ty, felts, legacy_storage)?;
}
} else {
// For uninitialized enum in new storage system, use 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure about that.
When we serialize a Ty:Enum, the enum should always be correctly initialized.
So, if option is None or if we cannot find the selected option among the options we should raise an error because the Ty:Enum has been incorrectly set.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But doesn't the new storage system actually allow enums to be None, hence the whole goal of bringing this new layout change? So that, in the case of an uninitialized enum, the option set is correctly None.

Because here we're just mimicking the exact same behaviour as the deserialize, but in the other way.
In the deserialize, when we have a selector of 0, we set the option to None.

So if someone deserializes a new layout Enum with a selector of 0, then tries to serialize it again, it will raise an error and lead to inconsistency across the two functions. We need to either not allow enum options to be None, or allow them.

Copy link
Collaborator

@remybar remybar Sep 26, 2025

Choose a reason for hiding this comment

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

In fact, the real behaviour at dojo-core level, for a DojoStore model (so non legacy one) is:

  • when you read an enum from the world storage and it is not yet initalized, the read variant index is 0.
  • when we deserialize an enum, the variant index is supposed to start from 1. If it is set to 0, we just return the configured default value of the enum. It could be the third variant with some specific data like MyEnum:ThirdVariant(array![1, 2, 3]), whatever.
  • we cannot serialize an uninitialized enum because we call the serialize() function on an enum variable which must be initialized first in Cairo.

Unfortunately, the schema (Ty) provided by the schema() function of a model contract does not contain the default value of an enum.

So, at sozo level, when we deserialize an uninitialized enum (because we may read uninitialized storage when calling sozo model get command), we just display <ENUM_NAME>::default(). See here:
https://github.com/dojoengine/dojo/blob/main/crates/sozo/ops/src/model.rs#L549-L560
There is a TODO to think about how to display the real default value instead of just <ENUM_NAME>::default() which is not so clear but it might not be so easy to do 😅

At torii level, I don't know why you may have to serialize some model data. I thought only the deserialize() function was used. Could you tell me more about that ? But, for me, it doesn't make sense to serialize an uninitialized enum. A possible use case would be to override an existing enum in the world storage to erase it but there is already delete functions at world level for that.

So, yes, if you could tell me the usecases you see in Torii for that, it could help me to understand the needs.

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