For the complete documentation index, see llms.txt. This page is also available as Markdown.

Testing

We provide a comprehensive testing suite for the whole Tycho stack. The suite facilitates end-to-end testing and ensures your protocol integration behaves as expected. For unit tests, please use standard Rust unit testing practices.

Find the suite in protocols/testing.

What does the suite test?

There are two test modes:

  • range — indexes and validates test cases defined in integration_test.tycho.yaml for specific block ranges.

  • full — indexes and validates the entire protocol history from creation to the latest block, without comparing specific component information.

Here's what the testing suite does:

  1. Runs Tycho Indexer with your Substreams implementation for a specific block range. If running on the range test mode, it also verifies that the components' state matches the expected states specified by the testing YAML file. This confirms that your Substreams package is indexable and that it outputs what you expect.

  2. Retrieves swap quotes using Tycho Simulation. This verifies that all necessary data for simulation is indexed and, for VM implementations, that the provided SwapAdapter contract works. It is important to know that the simulation engine runs entirely off-chain and only accesses the data and contracts you index (token contracts are mocked and don't need to be indexed)

  3. Encodes and simulates transactions using Tycho Execution against an RPC on an historical block. This ensures that your protocol swaps can be executed on chain and that the indexed data and quotes match onchain state and logic.

How to run

Prerequisites

Archive node

You need an EVM Archive node to fetch the state from a previous block. If you index only with Substreams, as in Tycho's production mode, you must sync blocks since the protocol's deployment date, which can take a long time. The archive node skips this requirement by fetching all the required account's storage slots on the block you specify in the testing yaml file.

The node also needs to support the debug_storageRangeAt method, which is required for our Token Quality Analysis.

As of July 2026, Erigon is the only major client supporting debug_storageRangeAt. The following API providers support archive nodes with debug_storageRangeAt:

  • Chainnodes (free account works)

  • Tatum (need any paid account for batch requests used by integration testing)

  • Chainstack (only with a $2700/mo dedicated archive node)

Test Configuration

Range Mode

Range mode tests specific block intervals to verify that your Substreams implementation correctly indexes and outputs expected component states. Please make sure that this block range is as small as possible so that the test runs quickly. The purpose of this test is to validate the logic on a few blocks only; for longer tests please see full mode test.

Configuration

Create an integration_test.tycho.yaml file in your Substreams directory with the following:

  • Target Substreams config file

  • SwapAdapter and construction arguments (for VM integrations)

  • Protocol system identifier

  • Expected protocol types

  • Test cases to execute

How It Works

Each test validates your integration across the specified block range:

  1. Index blocks: Indexes all blocks between start-block and stop-block

  2. Read state: Reads the indexed state at the last block the indexer committed, which is stop-block or stop-block + 1. The test fails if the committed block is outside that range, for example when --reuse-last-sync reuses a database indexed past stop-block

  3. Verify state: Confirms the indexed state matches expected component creation

  4. Simulate swap: Decodes every expected component and runs get_amount_out simulation (uses the provided SwapAdapter for VM integrations). The test fails if an expected component does not decode, unless you set skip_simulation for it

  5. Execute swap: Encodes a single swap and simulates its execution

  6. Validate consistency: Verifies that execution output matches simulation output

This ensures your indexing captures the correct protocol state and that simulation and execution remain consistent.

Full Mode

Full mode validates the complete protocol lifecycle—from indexing to live streaming—ensuring your integration works end-to-end in a production-like environment.

How It Works

1. Initial Indexing

The test indexes all blocks from start-block to the current block. Depending on the block range, this may take significant time.

Tip: Use the --reuse-last-sync flag to skip re-indexing on subsequent runs. This reuses the existing database state and syncs only new blocks, rather than starting from scratch.

2. Live Streaming

Once syncing catches up, the test begins streaming blocks live, simulating a production environment. For each block and each component, it:

  1. Simulates get_amount_out using the current block state

  2. Encodes a single swap and executes it in the current block

  3. Verifies that the execution output matches the simulation output

This validates that your indexing, simulation, and execution implementations are consistent and correct.

⚠️ If you encounter issues running the full test, please contact us for support.

Test Parameters

Here are the test parameters that you need to set:

1. initialized_accounts

This is a list of contract addresses that simulation requires, although their creation is not indexed within the test block range. Leave empty if not required.

Importantly, you use this config during testing only. Your Substreams package should still properly initialise the accounts listed here. This configuration only eliminates the need to include historical blocks that contain the initialisation events in your test data. This ensures tests are targeted and quick to run.

You can use the initialized_accounts config at two levels in the test configuration file:

  • global: accounts listed here are used for all tests in this suite;

  • test level: accounts listed here are scoped to that test only.

2. expected_components (for range mode)

This is a list of components whose creation you are testing. It includes all component data (tokens, static attributes, etc.). You don't need to include all components created within your test block range – only those on which the test should focus.

3. skip_balance_check

By default, this should be false. Testing verifies the balances reported for the component by comparing them to the on-chain balances of the Component.id . This should be false if:

  1. the Component.id does not correlate to a contract address;

  2. balances are not stored on the component's contract (i.e. they're stored on a vault).

If this skip is set to true, you must comment on why.

4. skip_simulation

By default this should be false . It should only be true temporarily if you want to isolate testing the indexing phase only. If set to true, you must comment on why.

While skip_simulation is false, the test fails when the decoder cannot decode the component's snapshot, for example because a required attribute is missing. The StateDecodingFailure warning in the log names the cause.

5. skip_execution

By default this should be false . It should only be true temporarily if you want to isolate testing the indexing and simulation phases only. If set to true, you must comment on why.

To be able to test execution, you need to provide the executor's runtime bytecode file.

  1. Declare your executor's contract name and constructor arguments in crates/tycho-execution/config/executor_deployments.json, keyed by chain and protocol. This file is the single source of truth for executor constructor arguments and later the deployment script reads the same entries.

  2. List your executor in crates/tycho-execution/contracts/test/RuntimeBytecodeFixtures.sol, pointing at the same (chain, protocol) entry. The fixture forks that chain at a block pinned per chain; pin a block of its own only when its constructor reads state that block does not carry.

  3. Regenerate the fixtures from crates/tycho-execution/contracts with forge script script/WriteRuntimeBytecodeFixtures.s.sol, then commit the YourExecutor.runtime.json it writes to protocols/testing/fixtures/. forge test fails in CI when a committed fixture is unlisted or out of date.

  4. Import the file in protocols/testing/src/execution.rs and add the corresponding entry to the EXECUTOR_MAPPING.

Block Compatibility Requirements

Testing during development

Since all crates live in the same monorepo, protocols/testing/Cargo.toml already references tycho-simulation and tycho-execution as local path dependencies. You can iterate directly without changing any Cargo.toml — your in-progress changes are picked up automatically.

Running Tests

We offer two approaches for running tests: local run and Docker run.

Local run works best when you're actively developing your integration. You can test individual phases (indexing, simulation, execution) in isolation and get faster iteration cycles for debugging. However, you'll need to handle additional setup and prerequisites yourself.

Docker run suits CI environments and final validation. You run the complete end-to-end test suite in an encapsulated environment, which eliminates the setup complexity you'd face otherwise. The actual test execution is fast once you have the images built, but every time you change something in your package, you'll need to rebuild the images—and that's the slow part. This approach makes most sense once your package is stable.

Here is how you can run the tests with each approach:

Prerequisites:

Before continuing, ensure the following tools and libraries are installed on your system:

  • Docker: Containerization platform for running applications in isolated environments.

  • Git: Version control tool

  • Rust: Programming language and toolchain

  • GCC: GNU Compiler Collection

  • libpq: PostgreSQL client library

  • OpenSSL (libssl): OpenSSL development library

  • pkg-config: Helper tool for managing compiler flags

  • Substreams CLI: Indexing tool that uses Rust modules to process blockchain data.

  • Tycho Indexer: The testing module runs a minified version of Tycho Indexer. You need to ensure that the latest version is correctly setup in your PATH and if it isn't you need to (re)install Tycho. Run the following command on your terminal to check the version:\

Step 1: Export Environment Variables

  • RPC_URL: The URL for the Ethereum RPC endpoint. This fetches the storage data.

  • SUBSTREAMS_API_TOKEN: The JWT token for accessing Substreams services. This token is necessary for authentication. Please refer to the Substreams Authentication guide to set up and validate your token.

  • RUST_LOG: Defines the log level for test output. For enhanced debugging:

    • Indexer: Run the testing module with Tycho indexer logs enabled: RUST_LOG=tycho_client=info,tycho_indexer=info,error

    • Simulation: Set the Tycho simulation module to debug level: RUST_LOG=tycho_simulation=debug,info

    • Execution traces: Set the Tycho testing module to debug level: RUST_LOG=tycho_test=debug,info

Step 2: Run a local Postgres test database using docker-compose.

In protocols/testing, run:

Step 3: Run tests

In protocols/testing, run:

Select range or full depending on your test mode. The test runner compiles the Substreams package's wasm file for you before it packs the .spkg, so you do not need to build it yourself.

Building the wasm yourself

Driving a package directly with substreams run or substreams gui still needs a compiled binary — see the implementation guide:

These are the optional arguments:

Complete example

If you want to run the range tests for ethereum-balancer-v2, use the following:

Prerequisites:

Before continuing, ensure the following tools and libraries are installed on your system:

  • Docker: Containerization platform for running applications in isolated environments.

Step 1: Export Environment Variables

  • RPC_URL: The URL for the Ethereum RPC endpoint. This fetches the storage data.

  • SUBSTREAMS_API_TOKEN: The JWT token for accessing Substreams services. This token is necessary for authentication. Please refer to the Substreams Authentication guide to set up and validate your token.

  • PROTOCOLS to test, separated by space and with optional filter.

Step 2: Build images

Build the image at the repository root path with:

Step 3: Run tests

In protocols/testing, run:

Complete example

If you want to run tests for ethereum-balancer-v2, use the following:

Note that only range tests are supported with Docker.

Installing or updating the Tycho Indexer version (Optional)

We provide a binary compiled for Linux x86/x64 architecture on our GitHub releases page.

Step 1: Download the pre-built binary

Navigate to the Tycho Indexer Releases page, locate the latest version (e.g.: 0.88.0) and download the tycho-indexer-x86_64-unknown-linux-gnu-{version}.tar.gz file.

Step 2: Extract the binary from the tar.gz

Open a terminal and navigate to the directory where the file was downloaded. Run the following command to extract the contents:

Step 3: Link the binary to a directory in your system's PATH:

NOTE: This command requires /usr/local/bin to be included in the system's PATH. While this is typically the case, there may be exceptions.

If /usr/local/bin is not in your PATH, you can either:

  1. Add it to your PATH by exporting it:

  2. Or create a symlink in any of the following directories (if they are in your PATH):

Step 4: Verify Installation

Troubleshooting

Slow tests

An integration test should take a maximum 5–10 minutes. If the tests take longer, here are key things you can explore:

  1. Ensure you have no infinite loops within your code.

  2. Ensure you're using a small block range for your test, ideally below 1,000 blocks. The blocks in your test only need to cover the creation of the component you are testing. Optionally, they can extend to blocks with changes for the component you want the test to cover. To help limit the test block range, you could explore the initialized_accounts config.

  3. Ensure you are not indexing tokens. Token contracts use a lot of storage, so fetching their historical data is slow. Instead, they are mocked on the simulation engine and don't have to be explicitly indexed. Make an exception if they have unique behavior, like acting as both a token and a pool, or if they are rebasing tokens that provide a getRatemethod.

Note: Substreams uses cache to improve the speed of subsequent runs of the same module. A test's first run is always slower than subsequent runs, unless you change the Substreams module's code.

Account not initialised

There are two main causes for this error:

  1. Your Substreams package is not indexing a contract that is necessary for simulations.

  2. Your test begins at a block that is later than the block on which the contract was created. To fix this, add the missing contract to the initialized_accounts test config.

Dev Cluster Tests

After your protocol is moved to our dev environment, it will be subject to constant indexing, simulation, and execution testing via a constant running pod in our cluster. If we find problems in any of these areas, we may reach out to you for help debugging.

Last updated

Was this helpful?