LogoLogo
Package RegistryThe Graph
  • Introduction
  • Getting Started
  • Tutorials
    • Develop Your First Substreams
      • on EVM
      • on Solana
        • Transactions & Instructions
        • Account Changes
      • on Cosmos
        • Injective
        • MANTRA
      • on Starknet
      • on Stellar
    • Publishing a Substreams Package
  • How-To Guides
    • Developing Substreams
      • on EVM
        • Exploring Ethereum
          • Mapping Blocks
          • Filter Transactions
          • Retrieve Events of a Smart Contract
      • on Solana
        • Explore Solana
          • Filter Instructions
          • Filter Transactions
        • SPL Token Tracker
        • NFT Trades
        • DEX Trades
      • on Cosmos
        • Injective
          • Simple Substreams Example
          • Foundational Modules
          • Dojo DEX USDT Volume Subgraph Example
    • Using a Substreams Sink
      • Substreams:SQL
      • Substreams:Subgraph
        • Triggers
        • Graph Out
      • Substreams:Stream
        • JavaScript
        • Go
      • Substreams:PubSub
      • Community Sinks
        • MongoDB
        • Files
        • Key-Value Store
        • Prometheus
    • EVM Extensions
      • Making eth_calls
    • Getting Started Using Rust and Protobuf
      • Rust
        • Option struct
        • Result struct
      • Protobuf Schemas
    • From Yellowstone to Substreams
  • Reference Material
    • Chains and endpoints
      • Ethereum Data Model
    • Never Miss Data
    • Development Container Reference
    • Substreams CLI
      • Install the CLI
      • Authentication
      • Substreams CLI reference
    • Substreams Components
      • Packages
      • Modules
        • Module types
        • Inputs
        • Output
        • Module handlers
        • Module handler creation
        • Indexes
        • Keys in stores
        • Dynamic data sources
        • Aggregation Windows
        • Parameterized Modules
      • Manifests Reference
    • Substreams Architecture
    • Graph-Node
      • Local Development
      • Publish to The Graph Network
    • Indexer Reference
      • Test Substreams Locally
    • Logging, Debugging & Testing
    • Change log
    • FAQ
  • Decentralized Indexing
    • What is The Graph?
Powered by GitBook
On this page
  • Using logs in Substreams
  • Unit Testing
  • Generating the Input of the Test

Was this helpful?

Edit on GitHub
  1. Reference Material

Logging, Debugging & Testing

In this reference, you will learn how to log arbitry inputs in your Substreams execution, and how to create unit tests for your modules.

Using logs in Substreams

It is possible to log arbitrary strings to the standard output of the Substreams execution. You will see these logs when running your Substreams using substreams gui or substreams run commands.

NOTE: When using the substreams gui command, use the L key to toggle logs.

To log something in Rust, use one of the following functions:

#[substreams::handlers::map]
fn map_program_data(blk: Block) -> Data {
    substreams::log::println("My log")
}

OR

#[substreams::handlers::map]
fn map_program_data(blk: Block) -> Data {
    substreams::log::debug!("My log")
}

OR

#[substreams::handlers::map]
fn map_program_data(blk: Block) -> Data {
    substreams::log::info!("My log")
}

Unit Testing

  1. The definition of the function is a standard Substreams module, which needs the #[substreams::handlers::map] macro. Because of the macro, the real signature of the function is more complex than just a couple of input parameters. Therefore, to facilitate the testing, all the logic is contained inside the _filtered_events function.

#[substreams::handlers::map]
fn filtered_events(query: String, events: Events) -> Result<Events, Error> {
    _filtered_events(query, events)
}
  1. The _filtered_events function, which contains the real logic, is just filtering the events (events: Events) matching the filter provided in the query (query: String).

/// _filtered_events is equal to [filtered_events] but exists only for unit testing purposes.
fn _filtered_events(query: String, mut events: Events) -> Result<Events, Error> {
    let matcher: substreams::ExprMatcher<'_> = substreams::expr_matcher(&query);

    events.events.retain(|event| {
        let keys = evt_keys(event.log.as_ref().unwrap());
        let keys = keys.iter().map(|k| k.as_str()).collect::<Vec<&str>>();

        matcher.matches_keys(&keys)
    });

    Ok(events)
}
  1. The filtered_events test is split into three parts:

    • Given: create the inputs of the function. In this case, we are reading a full Ethereum Block from a base-64-encoded file. Later in this doc, you will learn how to generate this file.

    • When: the actual execution of the function. The tests will filter all the events with address=0x5acc84a3e955bdd76467d3348077d003f00ffb97 because we know this event is contained in the input Block.

    • Expect: there are you assertions: the number of events returned must be greater than 0; all the events returned must have the 0x5acc84a3e955bdd76467d3348077d003f00ffb97 address.

#[test]
fn test_filtered_events() {
    // Given
    let block: Block =
        testing::read_block("./src/testdata/ethereum_mainnet_10500500.binpb.base64");

    // When
    let result = _filtered_events(
        "evt_addr:0x5acc84a3e955bdd76467d3348077d003f00ffb97".to_owned(),
        _all_events(block).unwrap(),
    )
    .expect("Failed to execute function");

    // Expect
    assert!(result.events.len() > 0);
    result.events.iter().for_each(|e| {
        let address: &Vec<u8> = &e.log.as_ref().unwrap().address;

        assert_eq!(
            Hex::encode(address),
            "5acc84a3e955bdd76467d3348077d003f00ffb97"
        );
    });
}

Generating the Input of the Test

The following example uses firecore to get the 5300300 Block of Ethereum Mainnet in base64-encoded file.

firecore tools firehose-single-block-client mainnet.eth.streamingfast.io:443 5300300 --output=bytes --bytes-encoding=base64  > /tmp/ethereum-mainnet-block-1.binpb.base64
PreviousTest Substreams LocallyNextChange log

Last updated 1 month ago

Was this helpful?

Testing a Substreams does not differ much from testing a standard Rust application. As an example, you can can refer to the tests in the . Let's take a look at the .

Usually, the input of a Substreams module must be the original source Block of the blockchain (or any of the related structures, such as Events, Calls...). You can get the raw Block of a blockchain using the tool (download the binary from the GitHub releases section).

You can include the base64 file in your Rust tests and use the Block Protobuf to .

Ethereum Foundational Modules Substreams
filtered_events test
firecore
decode the data