Skip to content

Hardening #4: Solidly V2 (Aerodrome/Velodrome) adapter - #16

Merged
KaiCode2 merged 3 commits into
codex/hardening-2-deferred-work-driverfrom
codex/hardening-4-solidly-v2-adapter
Jun 24, 2026
Merged

Hardening #4: Solidly V2 (Aerodrome/Velodrome) adapter#16
KaiCode2 merged 3 commits into
codex/hardening-2-deferred-work-driverfrom
codex/hardening-4-solidly-v2-adapter

Conversation

@KaiCode2

Copy link
Copy Markdown
Owner

Stacked on #15 (hardening #2). Base is the hardening-2 branch, so the diff is item #4 only. First new protocol adapter since the V2/V3/Balancer set.

What

A real adapter for ProtocolId::SolidlyV2 (was scaffold-only): cold-start + reactive + swap-sim, mirroring the Uniswap V2 adapter but for Solidly's unpacked reserves and stable/volatile invariants.

  • New types: SolidlyStorageLayout { reserve0_slot, reserve1_slot, token0_slot, token1_slot } (config-supplied — slot indices are fork-specific, no derivable default), SolidlyV2Metadata { token0, token1, stable, storage_layout }, ProtocolMetadata::SolidlyV2, and a solidly-v2 feature (in default).
  • SolidlyV2Adapter: event_sources (Sync); cold-start verifies reserve0/reserve1 (both mandatory, classified from SlotFetch so genuine-zero→PurgeSlots and archive-miss→VerifySlots stay distinct) + token slots (HotSlotsOnly/Lazy policies; Lazy defers tokens); decode_event Sync(uint256,uint256)two exact full-slot writes (no fetch); after_apply skipped→VerifySlots; simulate_swap via the pool's own getAmountOut(amountIn, tokenIn) through call_raw (stable/volatile math runs in-EVM — none reimplemented).

Tests

Manager-authored: solidly_cold_start_ready_warms_reserves_and_tokens, solidly_cold_start_zero_vs_failed_reserves_are_distinct_repairs, solidly_sync_writes_both_reserve_slots_through_runtime. cold_start_adoption 15→17, adapter_reactive 28→29.

Test plan

Full matrix green: fmt; clippy default + adapters,uniswap-v2,uniswap-v3,balancer-v2,solidly-v2 + --no-default-features (-D warnings); cargo test default + --no-default-features; cargo doc -D warnings. Spec: docs/hardening-4-solidly-v2-adapter-spec.md.

Follow-ups (documented in the spec)

  • An offline simulate_swap test with a mock Solidly pool fixture (the simulate_swap method is implemented + follows the proven run_quote pattern, but lacks a Solidly-specific test).
  • An #[ignore] RPC-parity test for a real Aerodrome/Velodrome V2 pool.
  • A verified velodrome_v2() layout default (slot indices need on-chain confirmation; config-supplied until then).

Implemented inline by the manager because subagent dispatch was returning 529 Overloaded; verified against the full matrix.

🤖 Generated with Claude Code

KaiCode2 and others added 3 commits June 24, 2026 15:20
Adds a real adapter for ProtocolId::SolidlyV2 (previously scaffold-only):
cold-start + reactive + swap-sim, mirroring the Uniswap V2 adapter but for
Solidly's unpacked reserves and stable/volatile invariants.

New types: SolidlyStorageLayout { reserve0_slot, reserve1_slot, token0_slot,
token1_slot } (config-supplied; slot indices are fork-specific so there is no
derivable default), SolidlyV2Metadata { token0, token1, stable, storage_layout },
ProtocolMetadata::SolidlyV2 variant + Debug arm, and a `solidly-v2` feature
(in default).

src/adapters/solidly_v2.rs (SolidlyV2Adapter): event_sources (Sync); cold-start
planner verifying reserve0/reserve1 (both mandatory, classified from SlotFetch so
genuine-zero -> PurgeSlots and archive-miss -> VerifySlots stay distinct) + token
slots, with HotSlotsOnly/Lazy policies (Lazy defers tokens); decode_event Sync
(uint256,uint256) -> two exact full-slot writes (no fetch); after_apply skipped ->
VerifySlots; simulate_swap via the pool's own getAmountOut(amountIn, tokenIn)
through call_raw (stable/volatile math runs in-EVM, none reimplemented).

Manager tests (cold_start_adoption.rs, adapter_reactive.rs):
solidly_cold_start_ready_warms_reserves_and_tokens,
solidly_cold_start_zero_vs_failed_reserves_are_distinct_repairs,
solidly_sync_writes_both_reserve_slots_through_runtime. cold_start_adoption 15->17,
adapter_reactive 28->29. Full matrix green: fmt; clippy default + adapters+solidly
+ no-default (-D warnings); tests default + no-default; cargo doc -D warnings.

Follow-ups (documented in the spec): an offline simulate_swap test with a mock
Solidly pool fixture, the #[ignore] RPC-parity test, and a verified velodrome_v2()
layout default (slot indices need on-chain confirmation — config-supplied until
then). Implemented inline by the manager because subagent dispatch was returning
529 Overloaded; verified against the matrix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A 17-agent adversarial audit of the inline-implemented Solidly adapter (11
confirmed findings) drove these fixes:

- HIGH: run_quote + its imports in sim.rs were gated on
  any(uniswap-v2,uniswap-v3,balancer-v2) but NOT solidly-v2, so
  `cargo build --no-default-features --features solidly-v2` failed to compile
  (masked by the all-features default build). Added solidly-v2 to the four
  cfg(any(...)) gates; all four protocol features now build in isolation.
- Robustness: a missing storage layout in decode_event returned MalformedLog,
  which made ReactiveRuntime::ingest_batch fail the ENTIRE batch (one
  un-cold-started Solidly pool would break reactive processing for every pool).
  Now returns `ignored()` — a config-missing event is skipped, not a
  batch-breaking error (there are no slots to target without a layout anyway).
- Validation: cold_start_planner now rejects a SolidlyStorageLayout whose slots
  collide (UnsupportedReason) instead of silently corrupting the verdict/token
  decode.
- Removed the dead after_apply override (Solidly's unpacked full-slot writes are
  never cold-skipped, unlike V2's masked write, so VerifySlots was unreachable;
  the trait default is correct) + documented why.
- Fixed overclaiming docs: getAmountOut also reads factory/stable/decimals and
  STATICCALLs the factory, so the quote is not reproducible from warmed reserves
  alone (live backend / fixture must reach those).

Tests (Solidly 3 -> 8): colliding-layout -> Unsupported; Lazy defers token slots
+ run_deferred warms them + HotSlotsOnly no-defer; offline simulate_swap (mock
pool getAmountOut) + revert -> Reverted; layout-less Sync doesn't mutate the
cache. Full matrix green incl. per-protocol isolation builds.

Follow-up (needs a Base/Optimism RPC + verified slots): a real-fork RPC-parity
test for an Aerodrome/Velodrome V2 pool, which is the only thing that validates
the real storage layout + getAmountOut/Sync ABIs and exercises the factory/stable
path (the offline mock is a trivial sload(0) stub).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the one remaining gap from the Solidly thorough-testing pass: the
offline mock (sload(0) stub) couldn't exercise the real getAmountOut/Sync
ABIs or the storage layout. This adds an env-gated #[ignore] parity test that
forks Base at a pinned block and validates against a live Aerodrome WETH/USDC
volatile pool:

  1. cold-start decodes the real token0/token1 from the configured token slots
     (proves slots 13/14),
  2. the configured reserve slots hold the pool's authoritative
     reserve0()/reserve1() (proves slots 20/21),
  3. simulate_swap (getAmountOut) == the same call via eth_call at the fork
     block (on-chain ground truth).

The storage layout was verified empirically (eth_getStorageAt scan matched
against the pool view fns) before being baked into the test as constants.
Confirmed Aerodrome keeps token0/token1 in storage (not immutable code), so
the 4-slot SolidlyStorageLayout holds.

Base RPC is taken from E2E_BASE_RPC_URL, or derived from E2E_RPC_URL by
swapping the Alchemy eth-mainnet host for base-mainnet. fork_cache/eth_call
helpers now take a block param so mainnet and Base forks share one harness;
the test entry gains the solidly-v2 required-feature.

Verified: all 4 RPC parity tests pass live (V2/V3/Balancer mainnet + Solidly
Base); fmt + clippy --all-targets --all-features -D warnings clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@KaiCode2
KaiCode2 merged commit 29ece86 into codex/hardening-2-deferred-work-driver Jun 24, 2026
1 check passed
KaiCode2 added a commit that referenced this pull request Jun 24, 2026
Deploy adapters pipeline + hardening to main (PRs #10, #13, #14, #15, #16)
@KaiCode2
KaiCode2 deleted the codex/hardening-4-solidly-v2-adapter branch June 25, 2026 09:55
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.

1 participant