# 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](https://doc.rust-lang.org/book/ch11-01-writing-tests.html).

{% hint style="info" %}
Find the suite in [/protocol\_testing](https://github.com/propeller-heads/tycho-protocol-sdk/tree/main/protocol-testing).
{% endhint %}

## 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](https://github.com/propeller-heads/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](https://github.com/propeller-heads/tycho-simulation). This verifies that all necessary data for simulation is indexed and, for [VM implementations](https://docs.propellerheads.xyz/tycho/overview/concepts#virtual-machine-vm-vs-native-custom), 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](https://github.com/propeller-heads/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](https://www.quicknode.com/docs/ethereum/debug_storageRangeAt) method, which is required for our Token Quality Analysis.

As of February 2026, Erigon is the only major client supporting debug\_storageRangeAt. The following API providers support archive nodes with debug\_storageRangeAt:

* [Chainnodes](https://www.chainnodes.org/)
* [Chainstack](https://chainstack.com/)

### 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](#full-mode) test.

#### Configuration

Create an [`integration_test.tycho.yaml`](https://github.com/propeller-heads/tycho-protocol-sdk/blob/main/substreams/ethereum-template-factory/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](https://docs.propellerheads.xyz/tycho/overview/concepts#protocolsystem) 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. **Verify state**: Confirms the indexed state matches expected component creation
3. **Simulate swap**: Runs `get_amount_out` simulation (uses the provided `SwapAdapter` for VM integrations)
4. **Execute swap**: Encodes a single swap and simulates its execution
5. **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.

:warning: 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](https://github.com/propeller-heads/tycho-protocol-sdk/blob/main/substreams/ethereum-template-factory/integration_test.tycho.yaml#L25): accounts listed here are used for all tests in this suite;
* [test level](https://github.com/propeller-heads/tycho-protocol-sdk/blob/main/substreams/ethereum-template-factory/integration_test.tycho.yaml#L39): 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.

{% hint style="danger" %}
**Code changes**

If the protocol you are integrating is not a vm integration, to be able to test simulation, you need to register it in `register_decoder_for_protocol` ([here](https://github.com/propeller-heads/tycho-protocol-sdk/blob/ea10bfa99a6524eec4725f2b37aab31de7299d63/protocol-testing/src/state_registry.rs#L17)). This is to match your protocol system name with the State that is used in Tycho Simulation.
{% endhint %}

#### 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. Export it using the helper script in [tycho-execution/foundry/scripts/export-runtime-bytecode.js ](https://github.com/propeller-heads/tycho-execution/blob/0454514f4f6ccff55dcaa8e3abbb4ac494d89eba/foundry/scripts/export-runtime-bytecode.js)(see the [README](https://github.com/propeller-heads/tycho-execution/blob/0454514f4f6ccff55dcaa8e3abbb4ac494d89eba/foundry/scripts/README.md#export-runtime-bytecode) for instructions on how).
2. Copy `YourExecutor.runtime.json` file to the SDK repository in [`tycho-protocol-sdk/evm/test/executors`](https://github.com/propeller-heads/tycho-protocol-sdk/tree/main/evm/test/executors) .
3. Import the file in [tycho-protocol-sdk/protocol-testing/src/execution.rs](https://github.com/propeller-heads/tycho-protocol-sdk/blob/3f31f85c157b1f860fcc9604376e2c11b1e8da0c/protocol-testing/src/execution.rs#L51) and add the corresponding entry to the `EXECUTOR_MAPPING` .

{% hint style="danger" %}
**Block Compatibility Requirements**

The `TychoRouter` requires post-Cancun blocks for execution. Testing must use block numbers after the Cancun upgrade.
{% endhint %}

{% hint style="info" %}
**Testing during development**

To test your protocol integration during development, update the `tycho-simulation` and `tycho-execution` dependencies in `protocol-testing/Cargo.toml` to point to your working branch/commit or to your local repository.

**Example:**

```toml
[dependencies]
tycho-simulation = { git = "https://github.com/propeller-heads/tycho-simulation", rev = "your-commit-hash" }
```

This allows you to iterate on your protocol implementation and run tests against your changes before submitting pull requests.
{% endhint %}

### 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:

{% tabs %}
{% tab title="Local run" %}
**Prerequisites:**

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

* [Docker](https://www.docker.com/): Containerization platform for running applications in isolated environments.
* [Git](https://git-scm.com/): Version control tool
* [Rust](https://www.rust-lang.org/): Programming language and toolchain
* [GCC](https://gcc.gnu.org/): GNU Compiler Collection
* [libpq](https://www.postgresql.org/docs/9.5/libpq.html): PostgreSQL client library
* [OpenSSL (libssl)](https://github.com/openssl/openssl): OpenSSL development library
* [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/): Helper tool for managing compiler flags
* [Substreams CLI](https://docs.substreams.dev/how-to-guides/installing-the-cli): Indexing tool that uses Rust modules to process blockchain data.
* [Tycho Indexer](https://github.com/propeller-heads/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.](#installing-or-updating-the-tycho-indexer-version-optional) Run the following command on your terminal to check the version:\\

  <pre class="language-bash"><code class="lang-bash">> tycho-indexer --version
  <strong>tycho-indexer 0.88.0 # should match the latest version published on GitHub
  </strong></code></pre>

**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](https://docs.substreams.dev/reference-material/substreams-cli/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`

```bash
export RPC_URL="https://ethereum-mainnet.core.chainstack.com/123123123123"
export SUBSTREAMS_API_TOKEN=eyJhbGci...
export RUST_LOG=protocol_testing=info,tycho_client=info,tycho_indexer=error,error
```

**Step 2: Build the substreams wasm file**

If you do not have one already, you must build the wasm file of the substreams package you wish to test. This can be done by navigating to the substreams package directory and running:

```bash
cargo build --target wasm32-unknown-unknown --release
```

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

In `/protocol-testing` , run:

```bash
docker compose -f ./docker-compose.yaml up -d db
```

**Step 4: Run tests**

In `/protocol-testing` , run:

```bash
cargo run -- range/full --package <package-name>
```

Select `range` or `full` depending on your test mode.

These are the optional arguments:

```bash
Options:
# Range tests
--package <PACKAGE>         # Name of the package to test
--match-test <MATCH_TEST>   # Run only tests matching name
--db-url <DB_URL>           # Database URL (default: postgres://postgres:mypassword@localhost:5431/tycho_indexer_0)
--rpc-url                   # RPC endpoint with trace support (required)
--chain                     # Chain to run the tests on (defaults to Ethereum)

# Full tests
--initial-block    # Start block (default: protocol creation block)
--stop-block       # End block (default: latest)

# Debugging
--vm-simulation-traces    # Enable VM simulation traces
```

**Complete example**

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

```bash
# Setup Environment Variables
export RPC_URL="https://ethereum-mainnet.core.chainstack.com/123123123123"
export SUBSTREAMS_API_TOKEN=eyJhbGci...
export RUST_LOG=info,protocol_testing=info,tycho_client=error

# Build Substreams wasm for BalancerV2
cd substreams
cargo build --release --package ethereum-balancer-v2 --target wasm32-unknown-unknown
cd ../protocol-testing

# Run Postgres DB using Docker compose
docker compose -f ./docker-compose.yaml up -d db

# Run test
cargo run -- range --package ethereum-balancer-v2 
```

{% endtab %}

{% tab title="Docker run" %}
**Prerequisites:**

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

* [Docker](https://www.docker.com/): 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](https://docs.substreams.dev/reference-material/substreams-cli/authentication) guide to set up and validate your token.
* **PROTOCOLS** to test, separated by space and with optional filter.

```bash
export RPC_URL="https://ethereum-mainnet.core.chainstack.com/123123123123"
export SUBSTREAMS_API_TOKEN=eyJhbGci...
export PROTOCOLS="ethereum-balancer-v2=weighted_legacy_creation ethereum-ekubo-v2"
```

**Step 2: Build images**

Build the image at the repository root path with:

```bash
docker buildx build -f protocol-testing/run.Dockerfile -t protocol-testing-test-runner:latest --load .
```

**Step 3: Run tests**

In `/protocol-testing`, run:

```bash
docker compose up -d && docker compose logs test-runner --follow
```

**Complete example**

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

```bash
# Setup Environment Variables
export RPC_URL="https://ethereum-mainnet.core.chainstack.com/123123123123"
export SUBSTREAMS_API_TOKEN=eyJhbGci...
export PROTOCOLS="ethereum-balancer-v2"

# Build image
docker buildx build -f protocol-testing/run.Dockerfile -t protocol-testing-test-runner:latest --load .

# Run test
cd protocol-testing/
docker compose up -d && docker compose logs test-runner --follow
```

Note that only `range` tests are supported with Docker.
{% endtab %}
{% endtabs %}

### **Installing or updating the Tycho Indexer version (Optional)**

{% tabs %}
{% tab title="Build locally (recommended)" %}
If you're running on a **MacOS** (either Apple Silicon or Intel) - or any architecture that is not supported by pre-built releases, you need to compile the Tycho Indexer:

**Step 1: Clone Tycho-Indexer repo**

```bash
git clone git@github.com:propeller-heads/tycho-indexer.git
cd tycho-indexer
```

**Step 2: Build the binary in release mode**

```bash
cargo build --release --bin tycho-indexer
```

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

```bash
sudo ln -s $(pwd)/target/release/tycho-indexer /usr/local/bin/tycho-indexer
```

**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:

   ```bash
   export PATH="/usr/local/bin:$PATH"
   ```
2. Or create a symlink in any of the following directories (if they are in your `PATH`):

```bash
/bin
/sbin
/usr/bin
/usr/sbin
/usr/local/bin
/usr/local/sbin
```

**Step 4: Verify Installation**

```bash
> tycho-indexer --version
tycho-indexer 0.88.0 # should match the latest version published on GitHub
```

{% endtab %}

{% tab title="Pre-built release (Linux AMD only)" %}
We provide a binary compiled for Linux x86/x64 architecture on our GitHub [releases](https://github.com/propeller-heads/tycho-indexer/releases) page.

{% hint style="warning" %}
This method will only work if you are running on a Linux with an x86/x64 architecture
{% endhint %}

**Step 1: Download the pre-built binary**

Navigate to the [Tycho Indexer Releases](https://github.com/propeller-heads/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:

<pre class="language-bash"><code class="lang-bash"><strong>tar -xvzf tycho-indexer-x86_64-unknown-linux-gnu-{version}.tar.gz
</strong></code></pre>

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

```bash
// Ensure the binary is executable:
sudo chmod +x tycho-indexer
// Create symlink
sudo ln -s $(pwd)/tycho-indexer /usr/local/bin/tycho-indexer
```

**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:

   ```bash
   export PATH="/usr/local/bin:$PATH"
   ```
2. Or create a symlink in any of the following directories (if they are in your `PATH`):

```bash
/bin
/sbin
/usr/bin
/usr/sbin
/usr/local/bin
/usr/local/sbin
```

**Step 4: Verify Installation**

```bash
> tycho-indexer --version
tycho-indexer 0.88.0 # should match the latest version published on GitHub
```

{% endtab %}
{% endtabs %}

## 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](#id-2.-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 `getRate`method.

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](#id-2.-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.
