# Common Linting Rules in GenLayer Contracts: Enforcing Determinism with genvm-lint

> Discover common GenLayer contract linting rules enforced by genvm-lint. Ensure determinism, security, and compatibility with over 20 essential checks.

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

---

**The GenVM linter (`genvm-lint`) enforces over 20 rules—including forbidden imports, storage type validation, and mandatory equivalence-principle blocks for non-deterministic calls—to ensure GenLayer contracts remain deterministic, secure, and compatible with the GenVM execution environment.**

GenLayer contracts are Python-based smart contracts that run on the GenVM (Generative Virtual Machine). To prevent non-deterministic behavior and security vulnerabilities, the genlayer-project-boilerplate repository integrates the `genvm-lint` validation tool, which analyzes contract code against strict linting criteria before deployment. These rules are documented in the project's [`CLAUDE.md`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/CLAUDE.md) and [`README.md`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/README.md) files, and are exemplified in the reference implementation at [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py).

## Forbidden Imports and Host System Access

The linter strictly prohibits imports that could access the host environment or introduce platform-specific behavior. According to the source analysis, importing modules such as **`os`**, **`sys`**, **`subprocess`**, or **`socket`** triggers immediate validation failures with the message: "Import of forbidden module 'os' detected."

This rule prevents contracts from performing file system operations, spawning processes, or creating network sockets, ensuring all validators execute identical bytecode without external dependencies.

```python

# ❌ Forbidden - triggers forbidden imports rule

import os
import socket

# ✅ Allowed - GenLayer SDK and deterministic libraries

from genlayer import gl

```

## Non-Deterministic Call Constraints

Any invocation of non-deterministic APIs must occur within an equivalence-principle block to guarantee consensus across the validator network. The linter validates that calls to **`gl.nondet.web`**, **`gl.nondet.exec_prompt`**, or similar APIs are wrapped in **`gl.vm.run_nondet`** or **`gl.eq_principle.strict_eq()`** contexts.

Unwrapped non-deterministic calls result in the error: "Non-deterministic call outside `run_nondet` block."

```python
@gl.public.write
def fetch_price(self) -> None:
    # ✅ Correctly wrapped for consensus validation

    result = gl.vm.run_nondet(
        lambda: gl.nondet.web.get("https://api.example.com/price"),
        lambda leader, validator: leader == validator
    )
    self.price = result.body

```

## Storage Type Validation

GenLayer contracts must use GenVM-specific storage containers rather than native Python types for persistent state. The linter enforces that state variables use **`TreeMap`**, **`DynArray`**, **`Array`**, **`u256`**, **`i256`**, or classes decorated with **`@allow_storage`**.

Using standard Python containers like **`dict`**, **`list`**, or **`set`** for contract storage produces the error: "Invalid storage type 'dict'; use TreeMap instead."

```python
class MyContract(gl.Contract):
    # ✅ Valid GenVM storage primitives

    balances: TreeMap[Address, u256]
    history: DynArray[str]
    
    def __init__(self):
        self.balances = TreeMap()
        
    # ❌ Invalid - native Python types lack deterministic serialization

    # temp_cache: dict[str, int] = {}

```

## Public Method Decorators and Type Safety

Every public method must declare its visibility and return type explicitly. The linter enforces two mandatory requirements:

- **Visibility Decorators**: All public methods must use **`@gl.public.view`** (read-only) or **`@gl.public.write`** (state-changing).
- **Return Type Annotations**: All public methods must declare return types using Python type hints (e.g., **`-> str`**, **`-> u256`**).

Violations generate: "Public method without `@gl.public` decorator" or "Public method lacks return type annotation."

```python
@gl.public.view
def get_balance(self, addr: Address) -> u256:
    """Returns the balance for a given address."""
    return self.balances.get(addr, 0)

@gl.public.write
def deposit(self, amount: u256) -> None:
    """Accepts a deposit and updates state."""
    self.balances[gl.message.sender_address] += amount

```

## Exception Handling Standards

Contracts must raise **`gl.vm.UserError`** (or subclasses) for contract-level validation failures rather than generic Python exceptions. The linter flags bare **`Exception`** raises and **`assert`** statements.

The enforced standard requires: "Use `gl.vm.UserError` for contract-level errors."

```python
@gl.public.write
def withdraw(self, amount: u256) -> None:
    current_balance = self.balances.get(gl.message.sender_address, 0)
    if amount > current_balance:
        # ✅ Correct error type for deterministic failure handling

        raise gl.vm.UserError("Insufficient balance")
    self.balances[gl.message.sender_address] -= amount

```

## Code Style and Formatting Rules

Beyond semantic validation, `genvm-lint` enforces PEP 8 compliance and limits line length to **120 characters** to maintain readability and consistency across the codebase.

## Running the GenVM Linter

To validate a contract against these 20+ rules, execute the linter from the project directory as referenced in [`README.md`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/README.md) and demonstrated with [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py):

```bash
genvm-lint check contracts/football_bets.py

```

This command performs static analysis to catch forbidden patterns, missing decorators, and type violations before deployment to the GenVM network.

## Summary

- **Forbidden Imports**: Blocks `os`, `sys`, `subprocess`, and `socket` to prevent host system access and ensure sandboxed execution.
- **Equivalence-Principle Wrapping**: Requires `gl.nondet` API calls to be wrapped in `gl.vm.run_nondet` or `gl.eq_principle.strict_eq()` for validator consensus.
- **Storage Constraints**: Mandates GenVM containers (`TreeMap`, `DynArray`, `u256`) instead of native Python `dict` or `list` for deterministic serialization.
- **Public API Contracts**: Requires `@gl.public.view` or `@gl.public.write` decorators plus explicit return type annotations on all public methods.
- **Standardized Errors**: Enforces `gl.vm.UserError` for contract logic failures instead of generic exceptions or assertions.
- **Formatting**: Enforces PEP 8 style with a 120-character line limit.

## Frequently Asked Questions

### What happens if I use a Python dictionary instead of TreeMap in GenLayer contracts?

The linter will reject the contract with the message "Invalid storage type 'dict'; use TreeMap instead." Native Python containers like `dict` and `list` do not guarantee deterministic serialization across different Python interpreter versions, so GenLayer requires using `TreeMap`, `DynArray`, or other GenVM storage primitives that ensure consistent state across all validators.

### How do I fix "Non-deterministic call outside run_nondet block" errors?

Wrap any call to `gl.nondet.web`, `gl.nondet.exec_prompt`, or similar non-deterministic APIs inside a `gl.vm.run_nondet()` block with an equivalence comparison function. This ensures validators can reach consensus by comparing results against the defined equivalence principle, as shown in the example contract at [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py).

### Why does the GenVM linter require explicit return types on public methods?

Explicit return type annotations ensure the GenVM can correctly serialize return values for cross-contract calls and consensus validation. The rule "Public method lacks return type annotation" prevents ABI mismatches and ensures consistent type handling across the distributed validator network.

### Can I use assert statements for input validation in GenLayer contracts?

No. The linter flags bare `assert` statements and generic `Exception` raises. You must raise `gl.vm.UserError` (or a subclass) for all contract-level validation failures. This standardization allows the GenVM to distinguish between intentional contract logic failures and unexpected system errors.