# Dojo Enums and Uninitialized Storage
We have identified an issue in Dojo storage when working with enums and uninitialized storage.
## Dojo Storage Overview
Before describing the issue, here's a brief summary of how Dojo storage works:
1. A model is defined as a Cairo struct.
2. This model is serialized using the `Serde` trait and written to world storage via `world.model_write(@m)`.
3. The world contract's storage acts as a database, where serialized data is written through syscalls to specific storage locations.
Since serialization is handled by the `Serde` trait, enums are serialized as follows:
1. The variant index is stored as the first `felt`.
2. If the variant contains a value (i.e., is not the unit type `()`), the serialized value occupies the remaining `felt`s.
For example, the `Option<T>` enum is serialized as:
```rust
enum Option<T> {
Some: T,
None,
}
let a = Option::None;
// Serialized = [0x1]
let b = Option::Some(2);
// Serialized = [0x0, 0x2]
```
In Starknet contracts, the Cairo compiler increments variant indices by `1`, ensuring that uninitialized storage defaults to a predictable variant.
Since Dojo uses `Serde`, this increment is not happening.
## Security Considerations
Given this behavior, consider the following model:
```rust
struct MyModel {
#[key]
id: u32,
score: Option<u32>,
}
```
If this model is read from storage before being explicitly written, the world’s storage remains uninitialized (filled with `0x0`s). This results in:
```rust
let my_key: u32 = 0x1234;
// Reading an uninitialized model from storage.
let m = world.read_model(my_key);
// This assertion will revert Cairo execution.
// assert(m == None)
if m.score.is_some() {
// Unexpected execution: `Some(0)` is returned instead of `None`.
} else {
// Expected behavior, but will not occur in this case.
}
```
Here, `Some(0)` is returned instead of `None` because `Some` is the first variant of `Option<T>`, leading to unintended behavior when relying on `score` for logic for uninitialized models.
For custom enums, consider:
```rust
enum MyEnum {
NotSet,
Submitted: u32,
Validated: felt252,
}
```
Reading uninitialized storage will return `MyEnum::NotSet`, as it is the first variant.
## Solutions at application level
1. Handling `Option<T>` at the Application Level
- Ensure models are explicitly initialized before being used.
- Avoid relying on `Option<T>` for initialization checks. Instead, use a separate `bool` or `integer` field, as these default to `0x0`.
2. Ordering Custom Enum Variants
- Define the default variant as the first variant to ensure correct behavior when reading uninitialized storage.
## Considerations for Dojo Core fix
We are investigating solutions that do not break existing storage in deployed worlds, though this presents challenges.
Possible Approaches:
1. **Introducing `DojoOption<T>`**
- A custom `DojoOption<T>` type where `None` is explicitly stored as `0x0`.
- Implementing `Into<Option<T>>` would allow seamless conversion.
- This approach is non-breaking but requires model upgrades or conversions while fetching data.
2. **Incrementing Variant Indices**
- Apply the Cairo compiler’s approach by shifting variant indices by `1`.
- `0x0` would then explicitly map to the default variant.
- This would break existing storage and lacks backward compatibility + some challenges for the impl.
3. **Modifying `Introspect` for `Option<T>`**
- Adjust `Option<T>` handling internally to map `None` to `0x0` and `Some` to `0x1`.
- Like the previous approach, this would break existing storage.
## Conclusion
If your project relies on `Option<T>` or custom enums, this issue may be critical. We recommend reviewing your usage and considering explicit initialization strategies when applicable.
For projects already on `mainnet`, upgrading the contract to modify logic or adding a dedicated initialization field can mitigate potential security risks.
This issue affects all versions since Dojo `1.0.0`.