Skip to content

WIP: Consume WAVS Test Harness - #209

Closed
JakeHartnell wants to merge 7 commits into
mainfrom
feat/consume-wavs-test-harness
Closed

JakeHartnell wants to merge 7 commits into
mainfrom
feat/consume-wavs-test-harness

Conversation

@JakeHartnell

@JakeHartnell JakeHartnell commented May 17, 2026

Copy link
Copy Markdown
Contributor

PoC companion to: Lay3rLabs/WAVS#1148

Consume wavs-test-harness end-to-end

Turns the PoC sketch on this branch into a runnable consumer of the new wavs-test-harness crate (Lay3rLabs/WAVS#1147). Three integration tests now exercise the full WAVS lifecycle in-process — real delta_neutral_strategy.wasm and wavs_farmer_aggregator.wasm running through wavs-engine, real signed envelopes, real deployed SmartVaultServiceHandler, real vault.executeRebalance — without bash, docker, or a running WAVS node.

What's shipped

Three test files on top of the existing common/ fixture:

File Scope Wall time
tests/test_rebalance_harness.rs Single-scenario E2E: strategy WASM → aggregator WASM → envelope → sign → real handler → real vault → hedge closed, eventId marked processed ~50s
tests/test_quorum_harness.rs 2-of-3 happy path + 1-of-3 revert + out-of-order signer revert with sort_signature_data recovery ~130s (3 tests × full setup)
tests/test_scenarios_harness.rs Two scenarios sequentially on one fixture (delta+ → oracle deviation skip) ~53s for both

Plus:

  • Fixture migration (crates/integration-tests/tests/common/): deleted anvil.rs, dropped impersonate_funded from oracle.rs, dropped mine_blocks from avantis.rs. Consumers now use wavs_test_harness::chain::{spawn_fork, impersonate_funded, mine_blocks, set_balance, enable_auto_impersonate} directly.
  • Shared helpers (tests/common/harness_handler.rs): deploy_service_manager, deploy_and_wire_vault_handler, spawn_mock_forecast_sidecar. ~140 lines covering everything the three harness tests share.
  • Strategy bypass (components/delta_neutral_strategy/src/lib.rs): one host::config_var("test_skip_pending_check") check that lets fork-based tests skip Avantis's "wait for pending orders" gate (Avantis's production keeper that prunes the counter doesn't run on a fork). Production operators don't set the env var, so it's a no-op outside tests.

The speed-multiplier story

This is what the harness gives you that bash/docker E2E can't:

Approach Setup cost Per-scenario cost 4 scenarios total
Bash E2E (scripts/integration-test.sh) ~30s spawn + deploy ~20s spawn + deploy + execute ~120s+
Standalone fork test per scenario ~38s fresh anvil + deploy ~12s execute ~200s
test_scenarios_harness (this PR) ~38s once ~7s per scenario ~66s

The killer detail: scenario 2 onwards costs <10s because the strategy WASM is already loaded into Wasmtime, the vault + service manager are already deployed, the fork is already warm. Adding scenarios is nearly free.

Required cross-repo coordination

This PR depends on a one-line pin change in WAVS to unblock alloy unification: Lay3rLabs/WAVS@5a1231b4f on branch feat/test-harness-1147. The change loosens =1.0.42 exact pins to caret (1.0.42) so downstream consumers can resolve newer alloy-rpc-types-eth versions without hitting the BlobTransactionSidecarVariant mismatch the PoC doc documented. Once #1147 lands on WAVS main, the wavs-test-harness git rev in crates/integration-tests/Cargo.toml should be repointed to a main commit (comment in the file flags this).

Trade-offs and known limitations

Worth surfacing before review so reviewers know what's deferred vs broken:

  • test_skip_pending_check strategy bypass. Avantis's pendingMarketOpenCount doesn't decrement on a fork because the production keeper bot never runs. We tried storage-cheating the counter via anvil_setStorageAt; it works for pendingOrderIds but zeroing the per-pair counter breaks Avantis's live-position bookkeeping (the trade disappears). A one-line config-var bypass in the strategy was the least invasive fix. Production behavior is unchanged.
  • Mock forecast sidecar. The strategy's V2 decision engine requires per-cycle VPIN/sigma/fee-richness data from a Python sidecar. Tests spin up a tokio TcpListener that returns canned values satisfying evaluate_skip_reason. The harness doesn't yet ship a generic HTTP-mock helper; this lives in common::harness_handler::spawn_mock_forecast_sidecar.
  • Snapshot/revert deferred. The original plan called for SnapshotGuard::take/revert to isolate scenarios. Anvil's evm_revert on a forked chain doesn't preserve node-level state like anvil_auto_impersonate_account — after revert, the next scenario's .from(deployer) tx silently hangs. The scenarios test uses sequential ordering instead, designed so state changes from prior scenarios don't invalidate subsequent assertions. The harness's snapshot primitive is the right tool for local-Anvil scenarios; the fork-revert interaction is brittle enough today that we sidestep it. (Worth filing upstream as a foundry/anvil issue.)
  • Wallet-bound provider workaround. The harness's Contract::deploy(...) and envelope::submit_envelope build TransactionRequests without a default from, requiring a wallet-bound provider. The legacy fixture uses anvil auto-impersonation instead. We deploy contracts and submit envelopes through the legacy provider with explicit .from(f.deployer) to avoid running two NonceFillers on the same account (they diverge after each tx).
  • Cargo.lock churn. The alloy bump cascades through ~9300 lines of lock-file diff. The functional change is small; the rest is cargo doing its job.

How to run

# Required env
export FORK_RPC_URL="https://base-mainnet.g.alchemy.com/v2/<key>"
export FUNDED_KEY="ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"

# Build WASM components first
cd components/delta_neutral_strategy && cargo build --release --target wasm32-wasip2 && cd ../..
cd components/aggregator && cargo build --release --target wasm32-wasip2 && cd ../..

# Or use the task runner
task build:wasi

# Each test independently
cargo test -p wavs-farmer-integration-tests --test test_rebalance_harness -- --nocapture
cargo test -p wavs-farmer-integration-tests --test test_scenarios_harness -- --nocapture
cargo test -p wavs-farmer-integration-tests --test test_quorum_harness -- --nocapture --test-threads=1

# Regression: legacy tests still compile
cargo check -p wavs-farmer-integration-tests --tests

Test plan

  • test_rebalance_harness passes against a fresh Base fork (~50s)
  • test_scenarios_harness passes; total wall time well under 100s
  • test_quorum_harness — all 3 scenarios pass with --test-threads=1
  • cargo check --tests reports zero errors across the whole crate
  • Legacy test_rebalance still compiles (it has its own pre-existing flakiness unrelated to this PR)
  • No new lints / warnings in cargo clippy

Future work

Items deliberately punted out of this PR. None block merge; all are tracked here so we don't lose them.

In the wavs-defi repo

  • Migrate remaining tests to the harness pattern. test_deposit.rs, test_oi_cap.rs, test_close_trade.rs, test_out_of_range.rs, test_withdraw.rs still build payloads manually and impersonate the service handler placeholder. Re-skin each on top of harness_handler::deploy_and_wire_vault_handler to exercise the same SmartVaultServiceHandler path the rebalance test uses.
  • Drop the manual bash E2E (scripts/integration-test.sh) once the harness suite covers the same ground — currently it's the only thing testing some of the close-and-reopen orchestration end-to-end.
  • Remove test_skip_pending_check if/when there's a cleaner way to deal with Avantis's stale counter (proper storage cheat, upstream cleanup function we missed, or moving to a local Anvil with MockAvantisTrading).
  • Repoint the wavs-test-harness git rev once Build reusable WAVS integration test harness WAVS#1147 lands on main. There's a comment in crates/integration-tests/Cargo.toml flagging this.

Upstream into wavs-test-harness

  • WavsServiceHandler deploy helper. Today the harness ships MockHandler (= SimpleServiceManager + SimpleSubmit). Real consumers want SimpleServiceManager + their own handler. Lift the pattern from common::harness_handler::{deploy_service_manager, deploy_and_wire_vault_handler} into the harness as a generic "deploy any IWavsServiceHandler-shaped contract and wire it to the service manager" helper.
  • HTTP-mock sidecar primitive. The pattern in common::harness_handler::spawn_mock_forecast_sidecar is reusable — any consumer with an external HTTP dependency needs the same shape. A mock::http_server(handler_fn) -> url helper in the harness would let consumers skip writing tokio TcpListener glue.
  • Pyth oracle helper. The harness's chain::oracle ships Chainlink V3 mocking. Pyth uses the same anvil_set_code pattern — would let us drop the last hand-rolled mock in common/oracle.rs.
  • Wallet-aware envelope submission. envelope::submit_envelope requires a wallet-bound provider. A variant that accepts an explicit from: Address would work with impersonated providers without forcing consumers to duplicate the function inline.

Anvil / foundry

  • evm_revert node-state preservation. When anvil_auto_impersonate_account(true) is set before a snapshot and the snapshot is reverted, the auto-impersonation flag is cleared (or otherwise becomes inert). Reproduces on anvil 1.7.1 against a Base mainnet fork via Alchemy. Worth filing upstream — the workaround in test_scenarios_harness is to use sequential ordering instead of snapshot/revert, but this limits the harness's most compelling primitive on fork-tier tests.

JakeHartnell and others added 2 commits May 16, 2026 03:44
Tracks the wavs-defi proof-of-concept consumer for the new
`wavs-test-harness` crate (Lay3rLabs/WAVS#1147).

Captures the intended port of `tests/test_rebalance.rs` to consume harness
primitives — `ChainProfile::load("base")` for addresses, `chain::mine_blocks`,
`envelope::sign_envelope`, `lifecycle::wait_until` — and documents the
real-world blocker that surfaced when we tried to add the path dependency:

  wavs-defi uses alloy 1.6.3; the WAVS workspace (and hence
  wavs-test-harness) is pinned to alloy 1.0.42. Adding a path dep triggers
  cargo workspace feature unification that breaks compilation of
  `alloy-rpc-types-eth` (BlobTransactionSidecarVariant vs
  BlobTransactionSidecar mismatch).

Three honest paths forward are listed (bump WAVS, pin wavs-defi, or publish
versioned). Resolving this is the highest-priority follow-up under #1147.

The harness *itself* is fully tested and verified end-to-end inside the
WAVS repo — see `WAVS/packages/test-harness` for runnable
`cargo test -p wavs-test-harness` + `cargo run --example minimal_local`.

No code changes to wavs-defi — this commit is doc-only.

Refs Lay3rLabs/WAVS#1147.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands the v1 consumer of the new wavs-test-harness crate (Lay3rLabs/WAVS#1147),
turning the PoC sketch in HARNESS-POC.md into a runnable test that drives the
full WAVS lifecycle on a Base fork:

  block trigger -> delta_neutral_strategy.wasm (real, in-process)
   -> aggregator.wasm (real, in-process)
   -> Envelope::from_operator_response + sign_envelope
   -> SmartVaultServiceHandler.handleSignedEnvelope (real, deployed)
   -> SmartVaultCore.executeRebalance (delta close phase)
   -> assert hedge closed + eventId marked processed

Key harness primitives exercised:
- `ChainProfile::load("base")` for Base address resolution
- `ServiceSpec` builder with full strategy config_var set (~25 vars from
  config/smart_vault.template.json) + `with_evm_local_chain("base", ...)`
- `InProcRunner::run_component_full` + `run_aggregator` execute the real
  strategy + aggregator WASM through wavs-engine + Wasmtime
- Aggregator emits a Submit action targeting `evm:base` + the deployed
  SmartVaultServiceHandler — proves the routing path
- `Envelope::from_operator_response` + `sign_envelope` produce the canonical
  signed envelope that `SimpleServiceManager.validate` accepts
- Replaces the legacy fixture's placeholder service handler with a real
  deployed SimpleServiceManager + SmartVaultServiceHandler, wired via
  `vault.setServiceHandler(handler_addr)`

Cargo.toml: adds wavs-test-harness + wavs-types as path deps to
../../../../../WAVS/packages/{test-harness,types}.

Cargo.lock: alloy-rpc-types-eth bumped to 1.7.3 to resolve the unification
issue the PoC sketch documented (BlobTransactionSidecarVariant vs
BlobTransactionSidecar). Requires the matching pin-loosening in WAVS
(Lay3rLabs/WAVS@5a1231b4f, branch feat/test-harness-1147).

Known limitations (deferred follow-ups):
- Avantis `pendingOrderIdsCount` lingers after `executeMarketOrders` on the
  fork — an Avantis state quirk, unrelated to the harness. The strategy's
  safety gate returns Vec::new() in this state, so the test falls back to a
  hand-built RebalancePayload (still routed through the harness's envelope
  + sign + submit + handler path).
- `install_chainlink_aggregator_v3` and `submit_envelope` need a
  wallet-bound provider with a default `from`; the legacy fixture uses
  anvil_auto_impersonate. v1 uses the legacy provider with explicit
  `.from(f.deployer)` for harness contract deploys and submission.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@vercel

vercel Bot commented May 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
wavs-vault-demo Ready Ready Preview, Comment May 17, 2026 3:16am

Request Review

…path dep (#1147)

Path deps to ../../../../../WAVS/packages/{test-harness,types} only resolve
on machines that happen to have WAVS checked out at that relative location.
CI and other developers don't.

Repin to a specific commit SHA on Lay3rLabs/WAVS's `feat/test-harness-1147`
branch (5a1231b4f) — the commit that loosened WAVS's exact alloy pins to
caret, which is required for cargo to unify the alloy crates between WAVS
and this repo.

When the harness PR lands on WAVS main, this rev should be repointed.

Verified: `cargo test -p wavs-farmer-integration-tests --test test_rebalance_harness`
still passes against the fork.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…-to-end (#1147)

Removes the manual-payload fallback from test_rebalance_harness. The strategy
WASM now runs the full V2 decision pipeline against fork state and emits the
RebalanceDelta payload itself:

  block trigger
    -> delta_neutral_strategy.wasm (real, full V2 decision pipeline)
    -> aggregator.wasm (real, routes Submit to evm:base)
    -> Envelope::from_operator_response + sign_envelope
    -> SmartVaultServiceHandler.handleSignedEnvelope (real, deployed)
    -> SmartVaultCore.executeRebalance
    -> hedge closed, eventId marked processed

Two changes were needed to make the strategy actually fire:

1. Strategy `test_skip_pending_check` config_var (lib.rs):
   The strategy's "wait for pending Avantis orders" safety gate trips on
   fork because Avantis's `pendingMarketOpenCount` counter doesn't decrement
   properly without the production keeper bot pruning it. Production
   operators don't set the env var, so this is a no-op outside tests.

2. Mock forecast sidecar (test_rebalance_harness.rs):
   The strategy's V2 decision engine requires a forecast sidecar
   (VPIN/sigma/fee-richness inputs — see adapters/forecast.rs). The test
   spins up a tokio HTTP mock on an ephemeral port that returns canned
   `tau=0.5, sigma_hat_sq=0.0001, forecast_kind=upstream, ...` satisfying
   `evaluate_skip_reason`. The strategy proceeds past the V2 preflight and
   the hedge_gate fires for the post-whale-swap delta.

The test now reads as a true E2E: strategy reads on-chain state, decides,
aggregates, signs, submits, vault executes. Runs in ~50s against a Base fork.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
JakeHartnell and others added 3 commits May 17, 2026 01:49
…or fork/impersonate/mine (#1147)

Replaces the legacy `common/anvil.rs`, `common/oracle.rs::impersonate_funded`,
and `common/avantis.rs::mine_blocks` with their `wavs_test_harness::chain::*`
equivalents. The legacy fixture (vault deployment, Pyth seed, vault config,
USDC funding) still owns work the harness doesn't yet cover, but the chain
control surface is now harness-driven:

- `common/anvil.rs` deleted (~45 lines). `common::setup` now calls
  `chain::spawn_fork(ForkOptions::from_env()?)` and layers the
  deployer-impersonation behavior (set_balance + impersonate_account +
  enable_auto_impersonate) inline.
- `common/oracle.rs::impersonate_funded` deleted; callers use
  `wavs_test_harness::chain::impersonate_funded` (returns `Result<()>`).
- `common/avantis.rs::mine_blocks` deleted; callers use
  `wavs_test_harness::chain::mine_blocks`.

All six legacy test files + the harness test updated for the new imports
and the `.await.unwrap()` shape required by the Result-returning helpers.

Verified: `test_rebalance_harness` still passes end-to-end (~50s) against
a Base fork. All other tests compile.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Adds `tests/test_quorum_harness.rs` exercising the SimpleServiceManager
quorum paths that single-operator `test_rebalance_harness` doesn't cover:

- `quorum_two_of_three_succeeds` — 3 operators, threshold 2, 2 sorted
  signers, handler accepts, vault.executeRebalance runs, hedge closes
- `quorum_one_of_three_reverts` — 1 signer below threshold, submission
  reverts at SimpleServiceManager.validate, vault state unchanged
- `quorum_out_of_order_then_sort_succeeds` — high-then-low order triggers
  InvalidSignatureOrder revert; `envelope::sort_signature_data` recovers

Also extracts `tests/common/harness_handler.rs` (~140 lines) which lifts
the SmartVaultServiceHandler sol! binding + the deploy/wire helpers out of
test_rebalance_harness.rs for shared use across the harness-driven test
files. test_rebalance_harness.rs is now ~100 lines smaller as a result.

Reverts surface at either send-time (alloy decodes the revert reason from
the RPC response) or receipt-time (Anvil returns receipt with status=false)
depending on whether the manager's custom error is in alloy's known set —
the assertions handle both shapes.

Verified: 3/3 quorum tests pass against a Base fork (~130s total with
--test-threads=1). test_rebalance_harness still passes.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…1147)

Adds `tests/test_scenarios_harness.rs` exercising two distinct strategy
decisions on top of a single fixture — the harness's speed-multiplier story
that bash + docker E2E loops can't match.

Scenarios driven against the same baseline (post-deposit hedged position):
1. **Delta+** (whale sells 800 WETH) → strategy emits `RebalanceDelta` with
   positive `hedgeAdjustment`
2. **Oracle deviation skip** (push Chainlink 30% off AMM) → strategy must
   SKIP for safety (empty response)

Measured wall time: ~53s total (fixture ~38s + both scenarios ~15s) vs
~100s if these were two standalone tests. The scenario-portion savings are
the real point — additional scenarios cost ~5-10s each instead of full
re-bootstrap.

### Why sequential, not snapshot/revert?

The plan called for `SnapshotGuard::take/revert`, but Anvil's `evm_revert`
doesn't preserve node-level state like `anvil_auto_impersonate_account` —
after a revert, the next scenario's `.from(deployer)` tx silently hangs
waiting for a signer. The harness's snapshot primitive is the right tool
for local-Anvil scenarios; on a fork the revert-state interaction is
brittle enough that sequential ordering is more reliable today. The
scenarios are designed so state changes from previous scenarios don't
invalidate subsequent assertions (#1 only emits a payload, doesn't submit
on-chain, so the hedge stays put for #2's deviation check).

Also lifts `spawn_mock_forecast_sidecar` into `common::harness_handler` for
reuse across the three harness-driven test files; test_rebalance_harness.rs
loses 55 lines.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

@layertau layertau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pushing this harness consumer forward — the single-scenario rebalance test does a good job wiring the real strategy/aggregator output through a real SmartVaultServiceHandler, and the PR clearly documents the fork-specific tradeoffs.

Pass 1 (spec compliance) found missing coverage relative to the PR description, so I’m stopping before the quality pass:

Critical — test_quorum_harness does not exercise the WAVS lifecycle promised by the PR.
The PR body says the three new integration tests exercise “real delta_neutral_strategy.wasm and wavs_farmer_aggregator.wasm running through wavs-engine, real signed envelopes, real deployed SmartVaultServiceHandler, real vault.executeRebalance”. However crates/integration-tests/tests/test_quorum_harness.rs builds a RebalancePayload manually (build_rebalance_payload) and submits it directly in an envelope; it never constructs a ServiceSpec, never runs InProcRunner, and never invokes the strategy or aggregator WASM. Please either update this test to drive the payload through the same strategy/aggregator harness path, or narrow the PR description/test scope so it doesn’t claim quorum coverage is an end-to-end WAVS lifecycle test.

Important — test_scenarios_harness only exercises the strategy, not the full submit path.
crates/integration-tests/tests/test_scenarios_harness.rs configures both WASMs, but the scenarios call run_component_full only and assert the strategy responses. They never run the aggregator, sign an envelope, submit to the handler, or verify vault.executeRebalance/event processing. That may be a useful strategy matrix, but it does not satisfy the PR’s stated “three integration tests now exercise the full WAVS lifecycle” claim. Please either add the missing aggregator/envelope/handler path where relevant, or adjust the PR/test naming and stated scope to match the actual coverage.

Verification note: I attempted cargo check -p wavs-farmer-integration-tests --tests. After setting local OpenSSL paths, compilation reached the test crate but could not proceed because this checkout lacked generated Foundry artifacts under contracts/out (and forge build was blocked by missing node_modules). I did not count that as a PR failure, but please ensure the documented setup commands produce those artifacts before re-requesting review.

@JakeHartnell

Copy link
Copy Markdown
Contributor Author

@layertau appreciate the comments, could you please make a PR into this one that addresses them?

@layertau

Copy link
Copy Markdown
Contributor

Opened #210 into this branch with the scope clarifications: #210

Note: after that merges, the parent PR body should be updated with the same narrower wording so the external spec matches the code: test_rebalance_harness is the full lifecycle test; quorum is handler/quorum coverage from encoded payloads; scenarios is a strategy-output matrix.

@JakeHartnell

Copy link
Copy Markdown
Contributor Author

@layertau upstream code has completely changed and this PR doesn't make sense to merge any more. I would love to see a similar PR based off the latest main branch testing the new Aave strategy.

@layertau

Copy link
Copy Markdown
Contributor

Acknowledged. Since the parent branch is obsolete, I’ll stop reviewing #209 and close the small helper PR I opened into it.

A fresh PR against latest main for the new Aave strategy sounds like the right follow-up; that should be tracked as a new implementation work stream rather than continuing this review thread.

@ueco-jb
ueco-jb deleted the feat/consume-wavs-test-harness branch May 26, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants