# Common `@allow_storage` Decorator Pitfalls in GenLayer Contracts

> Discover common @allow_storage decorator pitfalls in GenLayer contracts. Learn how incorrect placement breaks compilation and invalidates storage types. Avoid these errors for robust GenLayer development.

- Repository: [GenLayer Labs/genlayer-project-boilerplate](https://github.com/genlayerlabs/genlayer-project-boilerplate)
- Tags: best-practices
- Published: 2026-08-20

---

**The most common pitfall with the `@allow_storage` decorator is placing it *after* `@dataclass`, which causes the GenLayer compiler to ignore it entirely and reject the class as an invalid storage type.**

The `@allow_storage` decorator enables custom Python classes to persist in GenLayer contract storage, but it imposes strict constraints that differ from standard Python patterns. As implemented in the [genlayerlabs/genlayer-project-boilerplate](https://github.com/genlayerlabs/genlayer-project-boilerplate) repository, these constraints ensure deterministic state serialization—a core requirement for blockchain execution. This guide examines the nine most frequent developer errors, with concrete examples from the `FootballBets` contract in [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) and authoritative references from [`CLAUDE.md`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/CLAUDE.md).

## Decorator Order: The Silent Failure

The GenLayer compiler only recognizes `@allow_storage` when it appears **before** any other class decorator. When placed after `@dataclass`, the class is treated as a plain Python object, triggering linting errors about invalid storage types.

**Incorrect:**

```python
@dataclass
@allow_storage                     # ❌ Ignored by compiler

class WrongOrder:
    value: u256

```

**Correct (as in [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py)):**

```python
@allow_storage                     # ✅ Must be outermost

@dataclass
class Bet:
    id: str
    has_resolved: bool
    game_date: str
    resolution_url: str
    team1: str
    team2: str
    predicted_winner: str
    real_winner: str
    real_score: str

```

The `Bet` class in [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) demonstrates this pattern correctly—the decorator stack order ensures the compiler registers the class for storage serialization.

## Using Unsupported Native Containers

GenLayer storage cannot hold native Python `list` or `dict` objects because their memory layout is nondeterministic across executions. The approved container types are:

- `TreeMap[K, V]` — ordered key-value mapping
- `DynArray[T]` — dynamically sized array
- `Array[T, N]` — fixed-size array
- Primitive numeric types: `u256`, `i256`

**Incorrect:**

```python
@allow_storage
@dataclass
class BadBet:
    scores: list = []               # ❌ Native list rejected

    metadata: dict = {}             # ❌ Native dict rejected

```

**Correct:**

```python
@allow_storage
@dataclass
class FixedBet:
    scores: DynArray[str] = DynArray()   # ✅ GenLayer container

    metadata: TreeMap[str, str] = TreeMap()  # ✅ GenLayer mapping

```

## Mutable Default Values and Shared State

Even with approved container types, using `field(default_factory=list)` or similar patterns creates a shared mutable instance across all contract objects. This violates GenLayer's deterministic state principle.

**Problematic:**

```python
from dataclasses import field

@allow_storage
@dataclass
class RiskyBet:
    entries: DynArray[str] = field(default_factory=DynArray)  # ❌ Lint error

```

**Resolution:**

Initialize containers explicitly within contract methods rather than as field defaults, or use the direct instantiation pattern shown above with `DynArray()` when the type itself is allowed.

## Embedding Nondeterministic Objects

Storing fields that depend on `gl.nondet.*` functions—such as web calls (`gl.nondet.get_webpage`) or LLM prompts (`gl.nondet.prompt`)—makes persisted state nondeterministic. This breaks GenLayer's equivalence-principle checks, where multiple validators must reach identical conclusions.

Keep all nondeterministic logic strictly within contract methods, never as stored field values.

## Recursive and Circular References

A class that references itself directly or indirectly causes infinite serialization loops. Common patterns that fail:

- `TreeMap[Address, MyClass]` where `MyClass` contains another `TreeMap` with the same class
- Self-referencing linked list structures

**Avoid:**

```python
@allow_storage
@dataclass
class Node:                         # ❌ Self-reference breaks compiler

    value: u256
    next_node: Optional['Node']

```

**Prefer:**

Store identifiers and use separate mapping tables:

```python
@allow_storage
@dataclass
class FlatNode:
    node_id: u256
    value: u256
    next_id: Optional[u256]         # ✅ Reference by ID

# In contract: TreeMap[u256, FlatNode] for lookup

```

## Missing Type Annotations

GenLayer's compiler generates storage schemas from static type information. Omitting annotations or using `Any` prevents schema construction.

**Insufficient:**

```python
@allow_storage
@dataclass
class VagueData:
    unlabeled = 0                   # ❌ No annotation

    anything: Any                   # ❌ Ambiguous type

```

**Required:**

```python
@allow_storage
@dataclass
class PreciseData:
    counter: u256                   # ✅ Concrete type

    owner: Address                  # ✅ GenLayer primitive

    labels: TreeMap[str, DynArray[str]]  # ✅ Nested approved types

```

## Inheritance Limitations

Subclassing an `@allow_storage` class is not supported and breaks serialization. The flattening and composition approach remains the only viable pattern.

**Do not use:**

```python
@allow_storage
@dataclass
class BaseRecord:
    id: u256

@allow_storage                      # ❌ Inheritance unsupported

@dataclass
class ExtendedRecord(BaseRecord):
    extra: str

```

**Instead compose:**

```python
@allow_storage
@dataclass
class CoreFields:
    id: u256

@allow_storage
@dataclass
class FullRecord:
    core: CoreFields                # ✅ Composition

    extra: str

```

## Functions as Field Values

Callable objects, including functions and lambdas, cannot be serialized into blockchain state. Fields must contain only data.

**Invalid:**

```python
@allow_storage
@dataclass
class InvalidLogic:
    validator: Callable[[str], bool] = some_func  # ❌ Cannot serialize

```

## Unbounded Data Structure Size

Extremely large `DynArray` or `TreeMap` instances risk exceeding gas limits or causing performance bottlenecks during access operations. Design with explicit bounds or shard data across multiple mappings.

## Summary

- Apply `@allow_storage` **before** `@dataclass` or any other decorator
- Use only **GenLayer-approved container types**: `TreeMap`, `DynArray`, `Array`
- Avoid **mutable defaults** and **nondeterministic objects** in stored fields
- Prevent **circular references** by using identifier-based lookups
- Provide **explicit type annotations** for every field
- Do not use **inheritance** with storage-enabled classes
- Store **data only**, never functions or callables
- Design **bounded data structures** to respect gas limits

## Frequently Asked Questions

### Why does decorator order matter with `@allow_storage`?

The GenLayer compiler processes decorators from bottom to top. When `@allow_storage` appears after `@dataclass`, the dataclass transformation completes before the storage registration can intercept it, causing the compiler to miss the annotation entirely. This results in the class being rejected as an invalid storage type during linting.

### Can I use Python's standard `list` and `dict` in `@allow_storage` classes?

No. Native `list` and `dict` objects have nondeterministic memory representations that vary across Python implementations and executions. GenLayer requires deterministic serialization for consensus. Replace `list` with `DynArray` or `Array`, and `dict` with `TreeMap`—all defined in the GenLayer standard library.

### How do I handle optional or default values in storage classes?

Use the standard `Optional[T]` annotation for nullable fields. For defaults, avoid `field(default_factory=...)` with mutable types. Either initialize values explicitly in your contract methods, or use direct instantiation like `DynArray()` when the container type itself is storage-approved.