Skip to main content
[ DOCS · HARNESS ]

Harness

The harness is project-owned Rust setup code that runs before tick 0. Use it when an adapter needs real account bytes instead of zeroed bootstrap accounts.

What Is A Harness?

A harness is a small Rust crate under .riptide/harness/. Riptide builds it and runs it before the simulation starts. The harness creates concrete setup state: Anchor account bytes, SPL mints and token accounts, PDAs, oracle mocks, sibling CPI programs, and any external accounts your adapter observes.

Keep the boundary simple: the adapter declares the shape Riptide should drive and observe; the harness writes the bytes that make that shape boot before tick 0. If external dependency state must change between flows, move that behavior into a guided sim service instead of extending the harness or Riptide core.

Rule of thumb: if riptide lint passes but riptide adapt fails because accounts are zeroed, use a harness instead of adding protocol-specific setup to Riptide core.

Generate The Crate

riptide init --wizard can scaffold a TODO harness on the advanced path. In the default thin-init flow, /riptide-config decides whether one is needed. If the repo does not have one yet, generate it from the adapter:

riptide harness generate --adapter .riptide/adapters/<program>.toml

Then edit .riptide/harness/src/main.rs. The generated crate imports riptide_engine::harness and implements RiptideHarness::setup.

Common Setup

The harness context gives you helpers for the setup most Solana protocols need:

  • Declared accounts — check adapter accounts with ctx.require_declared_account.
  • Raw bytes — write shared or per-agent data with ctx.set_shared_account_data and ctx.set_agent_account_data.
  • SPL state — create mints, shared vaults, and per-agent token accounts with ctx.spl_mint, ctx.spl_token_account, and ctx.agent_spl_token_account.
  • Addresses — derive and bind PDAs with ctx.derive_pda, ctx.bind_shared_account, and ctx.bind_agent_accounts.
  • Programs — load a sibling program with ctx.load_program_from_so.
use riptide_engine::harness::{run_harness_cli, HarnessContext, RiptideHarness};

struct ProjectHarness;

impl RiptideHarness for ProjectHarness {
    fn setup(&self, ctx: &mut HarnessContext<'_>) -> anyhow::Result<()> {
        let authority = ctx.admin_pubkey();
        let mint = ctx.spl_mint("mint", authority, 1_000_000_000, 6)?;

        ctx.spl_token_account("vault", mint, authority, 500_000)?;

        for agent_idx in 0..ctx.agent_count() {
            let owner = ctx.agent_pubkey(agent_idx)?;
            ctx.agent_spl_token_account("user_ata", agent_idx, mint, owner, 100_000)?;
        }

        Ok(())
    }
}

fn main() -> std::process::ExitCode {
    run_harness_cli(ProjectHarness)
}

SPL Decoder Flow

The generic adapter can observe raw external account bytes through decoders. SPL Token account and mint support are presets over that generic layout layer, not protocol-specific integrations.

[accounts.vault_src]
kind = "shared"
space = 165
decoder = "spl_token_account"

[state_mapping]
"vault_src.amount" = "pool.reserve_src"

[observations]
"pool.reserve_src" = "uint"

Pair that adapter mapping with harness-created SPL token account bytes. Riptide decodes vault_src.amount every tick and the normal state_mapping surface drives coverage checks and invariants.

Validate

Keep validation bounded while you are authoring setup. Build the harness and run one seed first:

cargo build --release --quiet --manifest-path .riptide/harness/Cargo.toml
riptide run --adapter .riptide/adapters/<program>.toml --harness .riptide/harness --seeds 1 --seed-root 1337

Inspect the result before launching a larger sweep. The smoke should show successful mapped actions, nonzero or moving decoded observations, and passing coverage checks such as state_movement.

For larger runs, multiply agents × ticks × seeds before launching. A 1000-agent, 30-tick, 50-seed sweep schedules 1,500,000 planned events.

Agent Skill

The riptide-config skill can draft or repair this crate as part of the post-init setup loop. It reads the adapter, IDL, and source/tests, edits .riptide/harness/src/main.rs, then validates with a one-seed riptide run --harness smoke.