# How to Debug Contract Reverts in GenLayer: A Complete Developer Guide

> Debug contract reverts in GenLayer with direct_vm.expect_revert and print statements. This guide helps developers identify and fix revert issues efficiently in your GenLayer projects.

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

---

**Use `direct_vm.expect_revert("<message>")` in isolated tests to reproduce and assert contract reverts, combined with temporary `print()` statements to inspect state before the `raise` clause.**

GenLayer contracts are standard Python classes inheriting from `gl.Contract`. When a contract method raises an exception, the GenVM rolls back the transaction and returns a revert containing the exception message. This guide covers the exact workflow for debugging these reverts in the `genlayerlabs/genlayer-project-boilerplate` repository.

## Understanding How GenLayer Reverts Work

Every GenLayer contract revert originates from a Python `raise` statement. The GenVM catches this exception, discards all state changes, and surfaces the message to the caller.

In [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py), three distinct revert paths exist:

- `create_bet` raises `Bet already created` when duplicate bets are attempted
- `resolve_bet` raises `Bet already resolved` for double-resolution attempts
- `resolve_bet` raises `Game not finished` when match data shows incomplete status

These reverts protect contract integrity by enforcing business rules at the code level.

## Locating Revert Sources in Contract Code

The first debugging step is matching the revert message to its source. Search the contract for the exact string returned in the transaction receipt.

### Example: Finding the "Bet Already Created" Revert

In [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py), lines 73-75 implement this check:

```python
if bet_id in self.bets[gl.message.sender_address]:
    raise Exception("Bet already created")

```

### Example: Finding the "Bet Already Resolved" Revert

Lines 91-93 in the same file handle this case:

```python
if bet.has_resolved:
    raise Exception("Bet already resolved")

```

Line 97-99 adds another guard:

```python
if not is_finished:
    raise Exception("Game not finished")

```

Use `grep "Your revert message"` or your IDE's search to jump directly to the triggering condition.

## Reproducing Reverts with Direct-Mode Tests

Direct tests run contracts in-memory without blockchain dependencies. This isolation lets you mock external calls—web fetches, LLM prompts—and verify exact revert behavior.

### The `expect_revert` Context Manager

The test harness provides `direct_vm.expect_revert("<message>")` as a context manager. Any revert with a matching message passes the test; mismatches or missing reverts fail.

In [`tests/direct/test_resolve_bet.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_resolve_bet.py), lines 74-85 demonstrate testing double-resolution:

```python
def test_resolve_already_resolved_fails(direct_vm, direct_deploy, direct_alice):
    contract = direct_deploy("contracts/football_bets.py")
    direct_vm.sender = direct_alice

    # Create and resolve the bet first

    contract.create_bet("2024-06-20", "Spain", "Italy", "1")
    _setup_match_mocks(direct_vm, "1:0", 1)
    contract.resolve_bet("2024-06-20_spain_italy")

    # Second resolve must revert

    with direct_vm.expect_revert("Bet already resolved"):
        contract.resolve_bet("2024-06-20_spain_italy")

```

### Setting Up Mocks for Isolated Testing

External dependencies are mocked with `vm.mock_web()` and `vm.mock_llm()`. The `_setup_match_mocks` helper in [`tests/direct/test_resolve_bet.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_resolve_bet.py) configures these:

```python
def _setup_match_mocks(vm, score: str, is_finished: int):
    vm.mock_web("https://www.bbc.com/...", '<html>match data</html>')
    vm.mock_llm("extract score and status", f'{{"score": "{score}", "finished": {is_finished}}}')

```

This pattern removes network variability and lets you test edge cases deterministically.

## Adding Diagnostic Output for Deep Debugging

While `print()` statements are suppressed in production deployments, they appear in direct test output. Add temporary logging before `raise` statements to expose internal state.

### Example: Debugging Unexpected Reverts

Modify `resolve_bet` temporarily:

```python
def resolve_bet(self, bet_id: str) -> None:
    bet = self.bets[gl.message.sender_address][bet_id]
    print(f"DEBUG: bet_id={bet_id}, has_resolved={bet.has_resolved}, match_time={bet.match_time}")
    
    if bet.has_resolved:
        raise Exception("Bet already resolved")
    # ... remaining logic

```

Run the test with `pytest tests/direct/test_resolve_bet.py -v -s` to see debug output. Remove or comment these lines before committing.

## Step-by-Step Debugging Workflow

Follow this systematic approach to resolve any GenLayer contract revert:

1. **Capture the revert message** from the transaction receipt or test failure

2. **Locate the source** with `grep -r "revert message" contracts/`

3. **Analyze the condition** triggering the `raise`—check state variables, parameters, and external data

4. **Create a minimal direct test** that reproduces the exact state:
   - Set `direct_vm.sender` appropriately
   - Deploy with `direct_deploy("contracts/<file>.py")`
   - Mock external calls with `vm.mock_web()` and `vm.mock_llm()`

5. **Assert the revert** using `with direct_vm.expect_revert("<message>"):`

6. **Add debug prints** if the revert path is unclear, re-run, then remove them

7. **Fix the logic** and run `pytest tests/direct/ -v` to verify no regressions

## Key Files for Revert Debugging

| File | Purpose |
|------|---------|
| [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) | Core contract with `raise` statements at lines 73-75, 91-93, 97-99 |
| [`tests/direct/test_resolve_bet.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_resolve_bet.py) | Demonstrates `expect_revert` usage and mock setup |
| [`tests/direct/test_create_bet.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_create_bet.py) | Shows "Bet already created" revert testing |

## Summary

- **GenLayer reverts** are Python exceptions caught by the GenVM, rolling back state and returning the message
- **Locate reverts** by searching contract files for the exact error string
- **Test reverts** in isolation using `direct_vm.expect_revert("<message>")` with mocked dependencies
- **Debug state** with temporary `print()` statements visible only in direct test output
- **Verify fixes** by running the full direct test suite to prevent regressions

## Frequently Asked Questions

### What makes a GenLayer contract revert?

A GenLayer contract reverts when any method raises a Python `Exception`. The GenVM intercepts this, reverts all state changes, and returns the exception message to the caller. This differs from Ethereum's `require()` or `revert()` opcodes—it's standard Python exception handling wrapped in transaction semantics.

### How do I test that my contract reverts correctly?

Use `with direct_vm.expect_revert("exact message"):` in direct-mode tests. This context manager asserts that the enclosed code raises the specified revert. Combine it with `vm.mock_web()` and `vm.mock_llm()` to control external dependencies and test edge cases deterministically, as shown in [`tests/direct/test_resolve_bet.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_resolve_bet.py).

### Can I see print output from my contract when testing?

Yes—`print()` and `logger` output appears in direct test results but is suppressed in production. Add diagnostic prints before `raise` statements, run with `pytest -s`, then remove them before deploying. This is the recommended method for inspecting state that triggers unexpected reverts.

### Why does my test fail even though the contract reverts?

`expect_revert` requires an exact message match. Check for typos, whitespace differences, or dynamic message construction. Also ensure you're using the context manager correctly—it must wrap the call that triggers the revert, not subsequent assertions.