WIP: Consume WAVS Test Harness - #209
JakeHartnell wants to merge 7 commits into
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…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>
…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
left a comment
There was a problem hiding this comment.
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.
|
@layertau appreciate the comments, could you please make a PR into this one that addresses them? |
|
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: |
|
@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. |
|
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 |
PoC companion to: Lay3rLabs/WAVS#1148
Consume
wavs-test-harnessend-to-endTurns the PoC sketch on this branch into a runnable consumer of the new
wavs-test-harnesscrate (Lay3rLabs/WAVS#1147). Three integration tests now exercise the full WAVS lifecycle in-process — realdelta_neutral_strategy.wasmandwavs_farmer_aggregator.wasmrunning throughwavs-engine, real signed envelopes, real deployedSmartVaultServiceHandler, realvault.executeRebalance— without bash, docker, or a running WAVS node.What's shipped
Three test files on top of the existing
common/fixture:tests/test_rebalance_harness.rstests/test_quorum_harness.rssort_signature_datarecoverytests/test_scenarios_harness.rsPlus:
crates/integration-tests/tests/common/): deletedanvil.rs, droppedimpersonate_fundedfromoracle.rs, droppedmine_blocksfromavantis.rs. Consumers now usewavs_test_harness::chain::{spawn_fork, impersonate_funded, mine_blocks, set_balance, enable_auto_impersonate}directly.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.components/delta_neutral_strategy/src/lib.rs): onehost::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:
scripts/integration-test.sh)test_scenarios_harness(this PR)The killer detail: scenario 2 onwards costs
<10sbecause 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.42exact pins to caret (1.0.42) so downstream consumers can resolve neweralloy-rpc-types-ethversions without hitting theBlobTransactionSidecarVariantmismatch the PoC doc documented. Once #1147 lands on WAVS main, thewavs-test-harnessgit rev incrates/integration-tests/Cargo.tomlshould 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_checkstrategy bypass. Avantis'spendingMarketOpenCountdoesn't decrement on a fork because the production keeper bot never runs. We tried storage-cheating the counter viaanvil_setStorageAt; it works forpendingOrderIdsbut 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.TcpListenerthat returns canned values satisfyingevaluate_skip_reason. The harness doesn't yet ship a generic HTTP-mock helper; this lives incommon::harness_handler::spawn_mock_forecast_sidecar.SnapshotGuard::take/revertto isolate scenarios. Anvil'sevm_reverton a forked chain doesn't preserve node-level state likeanvil_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.)Contract::deploy(...)andenvelope::submit_envelopebuildTransactionRequests without a defaultfrom, 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 twoNonceFillers on the same account (they diverge after each tx).How to run
Test plan
test_rebalance_harnesspasses against a fresh Base fork (~50s)test_scenarios_harnesspasses; total wall time well under 100stest_quorum_harness— all 3 scenarios pass with--test-threads=1cargo check --testsreports zero errors across the whole cratetest_rebalancestill compiles (it has its own pre-existing flakiness unrelated to this PR)cargo clippyFuture 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
test_deposit.rs,test_oi_cap.rs,test_close_trade.rs,test_out_of_range.rs,test_withdraw.rsstill build payloads manually and impersonate the service handler placeholder. Re-skin each on top ofharness_handler::deploy_and_wire_vault_handlerto exercise the same SmartVaultServiceHandler path the rebalance test uses.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.test_skip_pending_checkif/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 withMockAvantisTrading).wavs-test-harnessgit rev once Build reusable WAVS integration test harness WAVS#1147 lands on main. There's a comment incrates/integration-tests/Cargo.tomlflagging this.Upstream into wavs-test-harness
WavsServiceHandlerdeploy helper. Today the harness shipsMockHandler(=SimpleServiceManager+SimpleSubmit). Real consumers wantSimpleServiceManager+ their own handler. Lift the pattern fromcommon::harness_handler::{deploy_service_manager, deploy_and_wire_vault_handler}into the harness as a generic "deploy anyIWavsServiceHandler-shaped contract and wire it to the service manager" helper.common::harness_handler::spawn_mock_forecast_sidecaris reusable — any consumer with an external HTTP dependency needs the same shape. Amock::http_server(handler_fn) -> urlhelper in the harness would let consumers skip writing tokio TcpListener glue.chain::oracleships Chainlink V3 mocking. Pyth uses the sameanvil_set_codepattern — would let us drop the last hand-rolled mock incommon/oracle.rs.envelope::submit_enveloperequires a wallet-bound provider. A variant that accepts an explicitfrom: Addresswould work with impersonated providers without forcing consumers to duplicate the function inline.Anvil / foundry
evm_revertnode-state preservation. Whenanvil_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 onanvil 1.7.1against a Base mainnet fork via Alchemy. Worth filing upstream — the workaround intest_scenarios_harnessis to use sequential ordering instead of snapshot/revert, but this limits the harness's most compelling primitive on fork-tier tests.