Commit graph

21 commits

Author SHA1 Message Date
openhands
78246ed399 fix: fix: Debug failing staking-safe attack in evolution fitness (#596)
Stake.nextPositionId starts at 654_321, so attack files cannot use literal
on-chain IDs (e.g. positionId=1 always reverts with PositionNotFound).

Fix AttackRunner to treat the JSONL positionId field as a 1-based index into
the list of positions created by stake ops during the current run:
- Add IStake.snatch returns (uint256) to the interface so the returned ID is
  captured.
- Track returned IDs in _stakedPositionIds[] (inserted in creation order).
- _executeUnstake resolves positionId to _stakedPositionIds[positionId-1]
  before calling exitPosition, matching the natural "unstake position 1"
  semantics in the attack DSL.

KRK approval for Stake was already present in _setup(); no other changes needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 07:10:13 +00:00
openhands
d6ca28ae32 fix: AttackRunner round-3 review findings
- **Bug**: Fix JSON malformation in _snapshotPositions — closing literal was '"}}}' (three
  braces) but only '"}}'  is needed (close discovery{} + positions{}). The third brace
  prematurely closed the root object, making every snapshot unparseable downstream.

- **Nit**: _executeStake local variable renamed taxRateIndex → taxRate to match the
  IStake interface and Stake.sol. JSONL field key '.taxRateIndex' is kept for backward
  compatibility with existing attack files; the comment and NatDoc header now say so.

- **Nit**: recenter_is_up now emits JSON null (not false) before the first recenter call,
  via a new _hasRecentered flag. Downstream parsers can distinguish "no recenter yet"
  from "last recenter moved price down" (false). _hasRecentered is set to true alongside
  _lastRecenterIsUp in the recenter handler.

- **Nit**: Added a comment to _logSnapshot explaining that pool.slot0() is a view call
  and forge-std finalises broadcast state before executing it, so tick/sqrtPrice are
  always post-broadcast accurate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 17:51:18 +00:00
openhands
297442083d fix: AttackRunner review findings — TVL accuracy, recenter capture, discovery ethValue
- **Bug**: `_positionEthValue` now sums both the ETH component and the KRK component
  (converted to ETH via `FullMath.mulDiv` at current sqrtPriceX96) so `lm_eth_total`
  correctly reflects LM TVL for all price ranges (below/in/above range).

- **Bug**: `recenter()` return value (`bool isUp` — price direction) is now captured in
  `_lastRecenterIsUp` state variable and emitted as `"recenter_is_up"` in every snapshot.
  Note: `recenter()` reverts on failure; `false` means price moved *down*, not a no-op.

- **Bug**: Discovery position now emits `"ethValue"` in its snapshot JSON object,
  matching the floor and anchor fields for symmetric automated parsing.

- **Warning**: `IStake.snatch` interface parameter renamed `taxRateIndex` → `taxRate` to
  match the actual `Stake.sol` signature (the value is a raw rate, not a lookup index).

- **Warning**: Unknown op codes in the JSONL file now emit a `console.log` warning
  instead of silently skipping, catching typos in attack sequences.

- **Nit**: `_setup()` now wraps 9 000 ETH (up from 1 000) to cover heavy buy sequences
  that would otherwise exhaust the adversary's WETH.

- **Nit**: `_computeVwapTick` documents the int128 overflow guard and its tick=0 sentinel
  meaning so callers can distinguish "VWAP unavailable" from tick zero.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 17:14:34 +00:00
openhands
c8453f6a33 fix: Backtesting: replay red-team attack sequences against optimizer candidates (#536)
- Add AttackRunner.s.sol: structured forge script that reads attack ops from a
  JSONL file (ATTACK_FILE env), executes them against the local Anvil deployment,
  and emits full state snapshots (tick, positions, VWAP, optimizer output,
  adversary balances) as JSON lines after every recenter and at start/end.

- Add 5 canonical attack files in onchain/script/backtesting/attacks/:
  * il-crystallization-15.jsonl  — 15 buy-recenter cycles + sell (extraction)
  * il-crystallization-80.jsonl  — 80 buy-recenter cycles + sell (extraction)
  * fee-drain-oscillation.jsonl  — buy-recenter-sell-recenter oscillation
  * round-trip-safe.jsonl        — 20 full round-trips (regression: safe)
  * staking-safe.jsonl           — staking manipulation (regression: safe)

- Add scripts/harb-evaluator/export-attacks.py: parses red-team-stream.jsonl
  for tool_use Bash blocks containing cast send commands and converts them to
  AttackRunner-compatible JSONL (buy/sell/recenter/stake/unstake/mint_lp/burn_lp).

- Update scripts/harb-evaluator/red-team.sh: after each agent run, automatically
  exports the attack sequence via export-attacks.py and replays it with
  AttackRunner to capture structured snapshots in tmp/red-team-snapshots.jsonl.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 02:08:06 +00:00
openhands
0fbc666c97 fix: No allowance set before \token0.transfer\ in swap callback (#339)
Add an explanatory comment to uniswapV3SwapCallback clarifying that
address(this) is pre-funded by _replaySwap before pool.swap() is
called, so no inline mint is required (unlike uniswapV3MintCallback).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 12:14:20 +00:00
openhands
af86ca1226 fix: address review feedback on BaselineStrategies and Reporter
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 13:43:49 +00:00
openhands
77f0fd82fd fix: Backtesting #6: Baseline strategies (HODL, full-range, fixed-width) + reporting (#320)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 13:08:53 +00:00
openhands
cf8e7ee6ee fix: address review feedback on PositionTracker and StrategyExecutor
- Fix fee attribution: distribute fees only to positions whose tick range
  contains the active tick at close time (in-range weight), not by raw
  liquidity. FLOOR is priced far below current tick and rarely earns fees;
  the old approach would over-credit it and corrupt capital-efficiency and
  net-P&L numbers. Fallback to raw-liquidity weighting with a WARN log
  when no position is in range.

- Warn on first-close skip: when _closePosition finds no open record
  (first recenter, before any tracking), log [TRACKER][WARN] instead of
  silently returning so the gap is visible in reports.

- Add tick range assertion: require() that the incoming close snapshot
  tick range matches the stored open record — a mismatch would mean IL
  is computed across different ranges (apples vs oranges).

- Fix finalBlock accuracy: logSummary now calls
  tracker.logFinalSummary(tracker.lastNotifiedBlock()) instead of
  lastRecenterBlock, so the summary reflects the actual last replay block
  rather than potentially hundreds of blocks early.

- Initialize lastRecenterBlock = block.number in StrategyExecutor
  constructor to defer the first recenter attempt by recenterInterval
  blocks and document the invariant.

- Extract shared FormatLib: _str(uint256) and _istr(int256) were
  copy-pasted in both PositionTracker and StrategyExecutor. Extracted to
  FormatLib.sol internal library; both contracts now use `using FormatLib`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 12:02:29 +00:00
openhands
cfcf750084 fix: Backtesting #5: Position tracking + P&L metrics (#319)
- Add PositionTracker.sol: tracks position lifecycle (open/close per
  recenter), records tick ranges, liquidity, entry/exit blocks/timestamps,
  token amounts (via LiquidityAmounts math), fees (proportional to
  liquidity share), IL (LP exit value − HODL value at exit price), and
  net P&L per position. Aggregates total fees, cumulative IL, net P&L,
  rebalance count, Anchor time-in-range, and capital efficiency accumulators.
  Logs with [TRACKER][TYPE] prefix; emits cumulative P&L every 500 blocks.

- Modify StrategyExecutor.sol: add IUniswapV3Pool + token0isWeth to
  constructor (creates PositionTracker internally), call
  tracker.notifyBlock() on every block for time-in-range, and call
  tracker.recordRecenter() on each successful recenter. logSummary()
  now delegates to tracker.logFinalSummary().

- Modify BacktestRunner.s.sol: pass sp.pool and token0isWeth to
  StrategyExecutor constructor; log tracker address.

- forge fmt: reformat all backtesting scripts and affected src/test files
  to project style (number_underscore=thousands, multiline_func_header=all).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:23:18 +00:00
openhands
84203294af fix: Backtesting #4: Deploy KrAIken contracts + recenter execution loop (#318)
- Add BacktestKraiken.sol: extends MockToken with Kraiken-compatible interface
  (dual mint overloads — public mint(address,uint256) for EventReplayer and
  restricted mint(uint256) for LiquidityManager; peripheryContracts() stubs
  staking pool as address(0))

- Add KrAIkenDeployer.sol: library deploying OptimizerV3Push3 + LiquidityManager
  on the shadow pool, wiring BacktestKraiken permissions, setting fee destination,
  and funding LM with configurable initial mock-WETH capital (default 10 ETH)

- Add StrategyExecutor.sol: time-based recenter trigger (configurable block
  interval, default 100 blocks); logs block, pre/post positions (Floor/Anchor/
  Discovery tick ranges + liquidity), fees collected, and revert reason on skip;
  negligible-impact assumption documented as TODO(#319)

- Modify EventReplayer.sol: add overloaded replay() accepting an optional
  StrategyExecutor hook; maybeRecenter() called after each block advancement
  without halting replay on failure

- Modify BacktestRunner.s.sol: replace tokenA/B with MockWETH + BacktestKraiken,
  integrate KrAIkenDeployer + StrategyExecutor into broadcast block; configurable
  via RECENTER_INTERVAL and INITIAL_CAPITAL_WETH env vars; executor.logSummary()
  printed after replay

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 09:00:22 +00:00
openhands
17b100ef2a fix: address AI review feedback (round 2) for #317 event replay
- Guard final drift sample with `idx % LOG_INTERVAL != 0` to prevent
  double-counting stats when totalReplayed is an exact multiple of
  LOG_INTERVAL (the loop's _logCheckpoint already fired for that state)
- Hoist pool.slot0() before the guard and pass finalSqrtPrice/finalTick
  to _logSummary(), eliminating the redundant slot0 read inside it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 07:55:53 +00:00
openhands
cd065275be fix: address AI review feedback for #317 event replay
- Cache pool.tickSpacing() as immutable in EventReplayer constructor
  to avoid a repeated external call per _replayMint() invocation
- Rename driftCount → driftCheckpoints for consistency with log label
- Add sqrtDriftBps to the per-checkpoint progress log line, using the
  now-live lastExpectedSqrtPrice field (previously written but never read)
- Guard _replaySwap(): skip and count events where amountSpecified ≤ 0,
  which would silently flip exact-input into exact-output mode
- Add a final drift sample after the while-loop for trailing events not
  covered by the last LOG_INTERVAL checkpoint
- Move EventReplayer construction outside the broadcast block in
  BacktestRunner (it uses vm.* cheat codes incompatible with real RPC)
- Change second vm.closeFile() from try/catch to a direct call so errors
  surface rather than being silently swallowed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 07:09:29 +00:00
openhands
a3eb406e46 fix: Backtesting #3: Replay historical Swap/Mint/Burn events against shadow pool (#317)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 06:17:54 +00:00
openhands
896fffb2e8 fix: Backtesting #3: Replay historical Swap/Mint/Burn events against shadow pool (#317)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 06:12:03 +00:00
openhands
70e49b2546 fix: suppress compiler warnings in BacktestRunner (#316)
- Add `view` to _parseSqrtPriceFromFile and _resolveSqrtPrice
- Remove unused IUniswapV3Pool import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 05:12:45 +00:00
openhands
96b06bd9fe fix: Backtesting #2: Foundry script skeleton + Uniswap V3 shadow pool deployment (#316)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 05:08:27 +00:00
openhands
c33bdbaad5 fix: address review feedback on fetch-events.ts (#315)
- Replace hardcoded Infura API key with INFURA_API_KEY env var; fail fast
  with a helpful message if unset and no --rpc-url is given
- Add onchain/script/backtesting/.gitignore (cache/) instead of relying on
  the opaque root pattern; remove force-tracked cache/.gitkeep (mkdirSync
  creates the directory at runtime)
- Document resume constraint: reliable only when both --start-block and
  --end-block are explicit, or --output is set
- Fix batch-number display: derive batchNum inside the loop from the actual
  `from` block so it stays correct when resumeFromBlock isn't BATCH_SIZE-aligned
- Guard log.logIndex === null consistently with blockNumber/transactionHash
- console.warn on decode errors instead of silently discarding them

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 22:45:40 +00:00
openhands
df89b8c2da fix: Backtesting #1: Event fetcher (Infura → JSON Lines cache) (#315)
Add onchain/script/backtesting/fetch-events.ts — a tsx script that:
- Fetches Swap/Mint/Burn events from a Uniswap V3 pool via Infura (Base mainnet)
- Batches eth_getLogs in 2 000-block chunks with 100 ms inter-batch delay
- Decodes each log with viem and writes one JSON Line per event
- Supports resume: reads last block from existing cache file on re-run
- Retries with exponential back-off on 429 / rate-limit errors
- Prints per-batch progress: "Fetching blocks X-Y... N events (B/T batches)"

Also adds package.json, tsconfig.json, and cache/.gitkeep.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 22:04:43 +00:00
openhands
4a6256b941 Revert "Merge pull request 'fix: Backtesting #6: Baseline strategies (HODL, full-range, fixed-width) + reporting (#320)' (#322) from fix/issue-320 into master"
This reverts commit 1e5ac0de80, reversing
changes made to 6526928b67.
2026-02-26 20:45:19 +00:00
openhands
9061f8e8f6 fix: Address AI review findings for backtesting baseline strategies (#320)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 16:39:47 +00:00
openhands
5205ea6f4a fix: Backtesting #6: Baseline strategies (HODL, full-range, fixed-width) + reporting (#320)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 16:11:15 +00:00