How to Test Contract Reverts in GenLayer Direct Mode

Use the direct_vm.expect_revert() context manager in your pytest functions to assert that smart contract calls raise specific error messages without deploying to a live network.

GenLayer's direct-mode testing framework allows developers to validate smart contract behavior locally by simulating the GenVM environment in-memory. This article demonstrates how to use the direct_vm fixture and its expect_revert method to test failure conditions in the genlayerlabs/genlayer-project-boilerplate repository. Mastering this pattern ensures your contracts handle edge cases correctly before deployment.

Understanding the direct_vm Fixture

The direct_vm fixture is provided by the GenLayer SDK's direct-mode plugin, configured in tests/direct/conftest.py. It instantiates a mock GenVM environment that loads your contract bytecode and exposes utilities for transaction simulation, including mock_web, mock_llm, clear_mocks, and the expect_revert context manager.

This fixture eliminates the need for a live network or Studio instance by running contracts directly against a Python-based VM implementation. You can set the transaction sender using direct_vm.sender = <account> and configure deterministic responses for external web calls and LLM invocations before asserting revert conditions.

How expect_revert Intercepts Exceptions

The expect_revert method creates a context manager that catches gl.vm.UserError exceptions (or standard Python Exception types) raised by contract code. When invoked with an error message substring, it verifies that the contract raises a matching error within the context block.

If the contract executes without reverting, or if the error message does not contain the expected substring, the test fails immediately. This behavior is implemented in the GenLayer testing utilities attached to the DirectVM class provided by the SDK.

Testing Common Revert Scenarios

Developers typically verify three categories of revert conditions: duplicate state entries, authorization failures, and unfinished external dependencies. Each scenario follows the same pattern: configure the contract state, optionally mock external responses, then wrap the failing call in with direct_vm.expect_revert("message"):.

Preventing Duplicate State Entries

In tests/direct/test_create_bet.py, tests verify that creating a bet with an existing key reverts to prevent duplicate entries. The contract stores bets in a mapping keyed by match identifiers, and attempts to overwrite existing entries trigger a revert.

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

    # First bet creation succeeds

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

    # Attempting to create the same bet again should revert

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

Enforcing State Machine Rules

The file tests/direct/test_resolve_bet.py demonstrates testing business logic constraints, such as preventing resolution of already-resolved bets or games that haven't finished. These tests verify that the contract maintains internal state integrity across multiple transactions.

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")
    
    # Mock successful match completion

    direct_vm.mock_web(r".*bbc\.com.*", {"status": 200, "body": "Result"})
    direct_vm.mock_llm(r".*Extract.*", '{"score": "1:0", "winner": 1}')

    # First resolution succeeds

    contract.resolve_bet("2024-06-20_spain_italy")

    # Second resolution must revert

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

Validating External Dependencies

When contract logic depends on web scrapes or LLM extractions, you must mock these responses to trigger specific revert paths. For example, verifying that resolving an unfinished game reverts requires simulating a pending match status.

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")
    
    # Mock incomplete match data

    direct_vm.mock_web(r".*bbc\.com.*", {"status": 200, "body": "Pending"})
    direct_vm.mock_llm(r".*Extract.*", '{"score": "-", "winner": -1}')

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

Complete Test Implementation Pattern

Combine state setup, mocking, and revert assertion into a comprehensive test workflow. Always deploy the contract using direct_deploy, set the sender address, configure any necessary mocks, execute the successful state transitions, then assert the revert condition.

import json

def test_unauthorized_resolution_reverts(direct_vm, direct_deploy, direct_alice, direct_bob):
    # Deploy and setup

    contract = direct_deploy("contracts/football_bets.py")
    direct_vm.sender = direct_alice
    contract.create_bet("2024-06-20", "Spain", "Italy", "1")
    
    # Switch to unauthorized user

    direct_vm.sender = direct_bob
    
    # Attempt unauthorized action

    with direct_vm.expect_revert("Not authorized"):
        contract.cancel_bet("2024-06-20_spain_italy")

Summary

  • The direct_vm fixture provides an in-memory GenVM simulation via tests/direct/conftest.py, enabling local contract testing without network deployment.
  • Use direct_vm.expect_revert("substring") as a context manager to assert that contract calls raise errors containing specific message text.
  • The context manager catches gl.vm.UserError and standard Python exceptions raised within the contract code executed in direct mode.
  • Configure external dependencies using direct_vm.mock_web() and direct_vm.mock_llm() before testing reverts that depend on oracle or web scrape data.
  • Test files such as tests/direct/test_resolve_bet.py and tests/direct/test_create_bet.py demonstrate production patterns for validating state machine integrity and duplicate prevention.

Frequently Asked Questions

What exception type does expect_revert catch?

The expect_revert context manager catches gl.vm.UserError exceptions raised by the GenLayer VM, along with standard Python Exception types. When the contract code executes raise gl.vm.UserError("message") or raises a standard exception, the context manager compares the error message against the expected substring provided in the method call.

Can I use regular expressions with expect_revert?

Based on the current implementation in genlayerlabs/genlayer-project-boilerplate, expect_revert performs substring matching rather than regex pattern matching. Supply the exact error message substring or unique identifier text that appears within the contract's revert message to ensure reliable test assertions.

Where is the direct_vm fixture defined?

The direct_vm fixture is defined in tests/direct/conftest.py and is provided by the GenLayer SDK's direct-mode testing plugin. This fixture instantiates a DirectVM class that mocks the complete GenLayer execution environment, including transaction context, external web calls, and LLM integrations.

Do I need a live network to test reverts in direct mode?

No. Direct mode operates entirely in-memory using the mock VM provided by the direct_vm fixture. This allows you to test contract reverts, state transitions, and external dependency integrations locally without connecting to GenLayer Studio or deploying to a testnet, significantly accelerating the development feedback loop.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →