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 practicesarrow-up-right.

circle-info

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 Indexerarrow-up-right 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 Simulationarrow-up-right. 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 Executionarrow-up-right 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 (you can use Alchemyarrow-up-right for example). 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_storageRangeAtarrow-up-right method, which is required for our Token Quality Analysis.

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.yamlarrow-up-right 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. 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.

⚠️ 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:

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.

triangle-exclamation

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 arrow-up-right (see the READMEarrow-up-right for instructions on how).

  2. Copy YourExecutor.runtime.json file to the SDK repository in tycho-protocol-sdk/evm/test/executorsarrow-up-right .

  3. Import the file in tycho-protocol-sdk/protocol-testing/src/execution.rsarrow-up-right and add the corresponding entry to the EXECUTOR_MAPPING .

triangle-exclamation

Block Compatibility Requirements

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

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

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:

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 Authenticationarrow-up-right 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-testing=debug,info

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:

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

In /protocol-testing , run:

Step 4: Run tests

In /protocol-testing , run:

Select range or full depending on your test mode.

These are the optional arguments:

Complete example

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

Installing or updating the Tycho Indexer version (Optional)

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?