Skip to main content
This page covers the smart contract implementation details. See Glossary.
Railnet provides a testing infrastructure built on Foundry that gives you over 100 STEAM compliance tests for free by inheriting from a single base contract. This guide shows you how to write tests for your Vehicle implementation.

Prerequisites

  • Foundry installed and configured
  • A Vehicle contract implementing IVehicle
  • Familiarity with the STEAM standard

Create the base test contract

Create a test file at test/vehicles/<your_vehicle>/<YourVehicle>Vehicle.t.sol. Inherit from STEAMSingleAssetTester to get the full STEAM test suite.
import {STEAMSingleAssetTester} from "test/test_utils/STEAM.SingleAsset.t.sol";
import {MyVehicle} from "src/vehicles/my_vehicle/MyVehicle.sol";

contract MyVehicleTest is STEAMSingleAssetTester {
    function setUp() external virtual {
        // 1. Deploy dependencies (mocks, assets)
        $asset = IERC20Metadata(address(new MockAsset()));

        // 2. Deploy your Vehicle
        ($vehicle,) = _deployMyVehicle(
            _deployCoreFactory(),
            MyVehicleFactory.SpawnParams({
                asset: address($asset),
                accessControl: _get_access_control(),
                feeManager: _get_fee_manager(),
                modulesManager: _get_modules_manager(),
                // ... other params
            })
        );
    }
}

Configure the test environment

Override internal hooks to provide optional components. By default, these return address(0).
function _get_access_control() internal virtual returns (ExternalAccessControl) {
    return ExternalAccessControl(address(0));
}

function _get_fee_manager() internal virtual returns (FeeManager) {
    return FeeManager(address(0));
}

function _get_modules_manager() internal virtual returns (ModulesManager) {
    return ModulesManager(address(0));
}

Add vehicle-specific tests

While STEAMSingleAssetTester covers the state machine, add tests for your protocol-specific logic.
function test_initialize_wrong_market_decimals() external {
    // Custom logic to verify vehicle-specific initialization
}

function test_deposit_with_custom_data() external {
    // Test protocol-specific data field handling
}

Test composition pattern

Test your Vehicle across different configurations by creating variant files that override the hooks.
1

Base tests (no extras)

MyVehicle.t.sol — the base file you created above.
2

With FeeManager

Create MyVehicle.WithFeeManager.t.sol:
import {MyVehicleTest} from "./MyVehicle.t.sol";

contract MyVehicleWithFeeManagerTest is MyVehicleTest {
    function _get_fee_manager() internal override returns (FeeManager) {
        return _deployFeeManager(/* params */);
    }
}
3

With ModulesManager

Create MyVehicle.WithModulesManager.t.sol and override _get_modules_manager().
4

With AccessControl

Create MyVehicle.WithAccessControl.t.sol and override _get_access_control().

Key helper methods

Use these from STEAMSingleAssetTester in your custom tests:
HelperDescription
_get_and_prepare_query(user, receiver, mode, amount, salt)Deals tokens, approves the Vehicle, returns a Query struct
_get_run_config(query)Returns a RunConfig for executing the query through the state machine
_compute_amount(uint256)Bounds a raw value to a reasonable test range (1 to 1000 units)
_unit(count)Converts a count to the asset’s decimals (e.g., _unit(1) = 1e18 for WETH)

Inherited test categories

When you inherit from STEAMSingleAssetTester, you automatically get tests for:
  • State machine — all valid and invalid state transitions
  • Input validation — zero amounts, invalid assets, unauthorized receivers
  • Access control — only the query owner or authorized roles can trigger transitions
  • Fee handling — deposit and redeem fees correctly applied
  • Module execution — interaction with protocol modules via ModulesManager

Running tests

# Run all tests for your Vehicle
forge test --match-contract MyVehicle

# Run with verbosity for debugging
forge test --match-contract MyVehicle -vvv

# Run a specific test
forge test --match-test test_initialize_wrong_market_decimals -vvv