Skip to main content
This page covers the smart contract implementation details. See Glossary.
Railnet ships a Foundry-based test harness that exposes 100+ STEAM conformance tests through a single base contract. Inherit from it to validate your Vehicle against the full state machine and lifecycle specification.

Prerequisites

  • Foundry installed and available on PATH
  • A Vehicle contract implementing IVehicle
  • Working knowledge of the STEAM standard

Create the base test contract

Place the test file at test/vehicles/<your_vehicle>/<YourVehicle>Vehicle.t.sol and inherit from STEAMSingleAssetTester to activate the conformance 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

The base tester exposes internal hooks for optional components. Each defaults to address(0); override the ones your Vehicle depends on.
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

STEAMSingleAssetTester covers the generic state machine. Extend it with tests that exercise your adapter’s protocol-specific logic — constructor guards, custom data field parsing, protocol edge cases.
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

Exercise the Vehicle under each supported configuration by creating variant test files that extend the base contract and override the component hooks.
1

Baseline (no optional components)

MyVehicle.t.sol — the base file defined above. All hooks resolve to address(0).
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

STEAMSingleAssetTester exposes the following internal helpers for use in custom test cases:
HelperDescription
_get_and_prepare_query(user, receiver, mode, amount, salt)Mints and approves the input asset, then returns a fully-formed Query struct
_get_run_config(query)Builds a RunConfig used to drive the query through the STEAM state machine
_compute_amount(uint256)Clamps a fuzzed value to a bounded range (1–1000 units)
_unit(count)Scales an integer count by the asset’s decimals (e.g., _unit(1) == 1e18 for WETH)

Inherited test categories

Inheriting from STEAMSingleAssetTester activates coverage across the following areas:
  • State machine — valid and invalid transitions across every STEAM state
  • Input validation — zero amounts, unsupported assets, invalid receivers
  • Access control — enforcement of caller permissions on lifecycle methods
  • Fee handling — correct accrual and settlement of deposit and redeem fees
  • Module execution — interaction with protocol modules through ModulesManager

Running tests

# Execute the full test suite for the Vehicle
forge test --match-contract MyVehicle

# Increase verbosity to surface traces and revert reasons
forge test --match-contract MyVehicle -vvv

# Target a single test case
forge test --match-test test_initialize_wrong_market_decimals -vvv