# How to Handle Contract Errors with expect_revert in GenLayer Tests

> Learn to handle contract errors in GenLayer tests using expect_revert. Assert failed contract calls with specific error messages for robust testing.

- 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>")` as a context manager to assert that contract calls fail with specific error messages in GenLayer Direct-Mode tests.**

GenLayer's Direct-Mode test harness provides first-class support for negative testing through the `expect_revert` helper. When writing tests for the `genlayerlabs/genlayer-project-boilerplate` repository, you can verify that your contracts correctly enforce business rules by asserting that specific method calls raise expected errors. This approach validates defensive programming patterns directly without deploying to a live network.

## How expect_revert Works in Direct-Mode

In GenLayer's Direct-Mode testing framework, contracts signal failures by raising plain Python `Exception` objects or `gl.vm.UserError` instances. When a contract method raises an exception, the VM catches it and returns it as a transaction revert. The `direct_vm.expect_revert` context manager wraps your contract call and verifies both that a revert occurs and that the error message matches the expected string exactly.

According to the source code in [`tests/direct/test_create_bet.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_create_bet.py), the comparison is case-sensitive and must be identical to the message raised by the contract. The helper records the revert, compares the message with the supplied string, and the test passes only if they match. If the method does not revert or the message differs, the test fails immediately.

## Testing Common Contract Error Conditions

The [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) contract demonstrates three defensive error patterns that you can validate using `expect_revert`. Each guard clause raises a plain `Exception` with a descriptive message when business rules are violated.

### Preventing Duplicate Entries

Contracts often need to enforce uniqueness constraints. In [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py), the `create_bet` method raises `"Bet already created"` when attempting to create a duplicate bet identifier.

The test in [`tests/direct/test_create_bet.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_create_bet.py) validates this protection:

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

    contract.create_bet("2024-06-20", "Spain", "Italy", "1")

    # The second creation should revert with "Bet already created"

    with direct_vm.expect_revert("Bet already created"):
        contract.create_bet("2024-06-20", "Spain", "Italy", "2")

```

### Blocking Invalid State Transitions

State machines require protection against invalid transitions. The `resolve_bet` method raises `"Bet already resolved"` when attempting to resolve a bet that has already been finalized.

The corresponding test in [`tests/direct/test_resolve_bet.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_resolve_bet.py) establishes the valid state first, then asserts the failure:

```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

    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")   # first resolution succeeds

    # Second resolution must revert with "Bet already resolved"

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

```

### Validating External Preconditions

Contracts frequently depend on external data conditions. The `resolve_bet` method raises `"Game not finished"` when attempting to resolve a bet for a match that has not yet completed, as implemented in [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py).

The test verifies this guard by mocking an unfinished game state:

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

    contract.create_bet("2024-06-20", "Spain", "Italy", "1")
    _setup_match_mocks(direct_vm, "-", -1)  # mock a match that is not finished

    # Resolve should revert with "Game not finished"

    with direct_vm.expect_revert("Game not finished"):
        contract.resolve_bet("2024-06-20_spain_italy")

```

## Best Practices for Negative Testing

When implementing error handling with `expect_revert` in the `genlayer-project-boilerplate` test suite, follow these guidelines to ensure reliable test coverage:

- **Raise plain exceptions** in contract code. GenLayer's linter forbids custom exception hierarchies; a simple `raise Exception("message")` works correctly and is captured by the VM.

- **Match the exact string** in `expect_revert`. The comparison is case-sensitive and must be identical to the message raised by the contract, including whitespace and punctuation.

- **Scope the VM sender** before the call. The revert check uses `gl.message.sender_address` internally, so you must set `direct_vm.sender` to the appropriate test account before entering the `expect_revert` block.

- **Isolate negative paths** into dedicated test functions. Keep positive-logic tests separate from negative assertions to maintain clarity and ensure that each failure mode is explicitly documented, as demonstrated in [`tests/direct/test_create_bet.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_create_bet.py) and [`tests/direct/test_resolve_bet.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_resolve_bet.py).

## Summary

- Use `direct_vm.expect_revert("message")` as a context manager to assert specific contract failures in GenLayer Direct-Mode tests.
- Contract errors must raise plain `Exception` objects; custom exception hierarchies are not supported by the linter.
- The error message passed to `expect_revert` must match the contract's raised exception string exactly, including case sensitivity.
- Always set `direct_vm.sender` before testing reverts, as the VM relies on `gl.message.sender_address` for transaction context.
- Reference implementations are available in [`tests/direct/test_create_bet.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_create_bet.py) and [`tests/direct/test_resolve_bet.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_resolve_bet.py) within the `genlayer-project-boilerplate` repository.

## Frequently Asked Questions

### What exception type should I raise in my GenLayer contract to trigger expect_revert?

Raise a plain Python `Exception` with a descriptive string message. According to the GenLayer linter rules used in `genlayer-project-boilerplate`, custom exception hierarchies are forbidden, so `raise Exception("Bet already created")` is the correct pattern. The VM catches these standard exceptions and converts them into reverts that `expect_revert` can detect.

### Does the message in expect_revert need to match exactly?

Yes, the comparison requires an exact string match. The `expect_revert` helper performs a case-sensitive comparison between the string you provide and the exception message raised by the contract. If the message differs by even a single character, the test will fail with a mismatch error.

### Why must I configure direct_vm.sender before using expect_revert?

The Direct-Mode VM uses `gl.message.sender_address` to contextualize the transaction sender when executing contract code. Since `expect_revert` executes the contract call within the VM to capture the exception, you must explicitly set `direct_vm.sender` to the appropriate test account (such as `direct_alice`) before entering the context manager block.

### Can I use custom exception classes from Python's standard library?

No. While Python allows importing exceptions like `ValueError` or `RuntimeError`, GenLayer's testing infrastructure and linter specifically expect plain `Exception` instances for contract-level errors. The `direct_vm.expect_revert` helper is optimized to catch these base exceptions as raised by the contract bytecode execution environment in [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py).