- getLiquidityParams() now reverts with "OptimizerV3Push3: not for production use" instead
of silently returning zeroed bear-mode defaults; LiquidityManager.recenter() already has
a try/catch fallback so backtesting is unaffected
- Added @custom:experimental NatSpec annotation to the contract marking it as a transpiler
harness / backtesting stub only
- DeployBase.sol now validates any pre-existing optimizer address by calling getLiquidityParams()
and reverting if it fails, blocking accidental wiring of OptimizerV3Push3 as a live optimizer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract transpiler output into OptimizerV3Push3Lib so both OptimizerV3
and OptimizerV3Push3 delegate to the same canonical copy. Future
transpiler changes now require only one edit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the orphaned NatSpec block (originally for calculateParams) from
above getLiquidityParams to directly precede calculateParams, and give
getLiquidityParams only its own @inheritdoc block.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Optimizer: add `is IOptimizer` and mark getLiquidityParams() with
`override`, making the interface conformance explicit at the base level.
OptimizerV3 inherits it transitively via Optimizer.
- OptimizerV3Push3: add `is IOptimizer` and implement getLiquidityParams()
that calls calculateParams() with zeroed inputs, returning bear-mode
defaults (ci=0, anchorShare=0.3e18, anchorWidth=100, discoveryDepth=0.3e18).
Behaviour is identical to the previous try/catch fallback used by
LiquidityManager and the backtesting deployer.
- Update backtesting comments to reflect that getLiquidityParams() now
exists on OptimizerV3Push3 (returns bear defaults via zeroed inputs).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes#635
## Changes
The implementation is complete and committed. All 211 tests pass.
## Summary of changes
### `onchain/src/Optimizer.sol`
- **Replaced raw slot inputs** with normalized indicators in `getLiquidityParams()`:
- Slot 2 `pricePosition`: where current price sits within VWAP ± 11 000 ticks (0 = lower bound, 0.5e18 = at VWAP, 1e18 = upper bound)
- Slot 3 `volatility`: `|shortTwap − longTwap| / 1000 ticks`, capped at 1e18
- Slot 4 `momentum`: 0 = falling, 0.5e18 = flat, 1e18 = rising (5-min vs 30-min TWAP delta)
- Slot 5 `timeSinceRecenter`: `elapsed / 86400s`, capped at 1e18
- Slot 6 `utilizationRate`: 1e18 if current tick is within anchor position range, else 0
- **Extended `setDataSources()`** to accept `liquidityManager` + `token0isWeth` (needed for correct tick direction in momentum/utilizationRate)
- **Added `_vwapToTick()`** helper: converts `vwapX96 = price × 2⁹⁶` to tick via `sqrt(vwapX96) << 48`, with TickMath bounds clamping
- All slots gracefully default to 0 when data sources are unconfigured or TWAP history is insufficient (try/catch on `pool.observe()`)
### `onchain/src/OptimizerV3Push3.sol`
- Updated NatSpec to document the new `[0, 1e18]` slot semantics
### New tests (`onchain/test/`)
- `OptimizerNormalizedInputsTest`: 18 tests covering all new slots, token ordering, TWAP fallback, and a bounded fuzz test
- `mocks/MockPool.sol`: configurable `slot0()` + `observe()` with TWAP tick math
- `mocks/MockLiquidityManagerPositions.sol`: configurable anchor position bounds
Co-authored-by: openhands <openhands@all-hands.dev>
Reviewed-on: https://codeberg.org/johba/harb/pulls/649
Reviewed-by: review_bot <review_bot@noreply.codeberg.org>
Three defensive layers so every Push3 program runs without reverting:
Layer A (transpiler/index.ts): assign bear defaults (CI=0, AS=0.3e18,
AW=100, DD=0.3e18) to all four outputs at the top of calculateParams.
Any output the evolved program does not overwrite keeps the safe default.
Layer B (transpiler/transpiler.ts): graceful stack underflow — dpop/bpop
return '0'/'false' instead of throwing, and the final output-pop falls
back to bear-default literals when fewer than 4 values remain on the
stack. Wrong output count no longer aborts transpilation.
Layer C (transpiler/transpiler.ts + index.ts): wrap the entire function
body in `unchecked {}` so integer overflow wraps (matching Push3), and
emit `(b == 0 ? 0 : a / b)` for every DYADIC./ (div-by-zero → 0,
matching Push3 no-op semantics).
Layer 2 (Optimizer.sol getLiquidityParams): clamp the three fraction
outputs (capitalInefficiency, anchorShare, discoveryDepth) to [0, 1e18]
after abi.decode so a buggy evolved program cannot produce out-of-range
values even if it runs without reverting.
Regenerated OptimizerV3Push3.sol with the updated transpiler; all 193
tests pass (34 Optimizer/OptimizerV3Push3 tests explicitly).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- Add `require(averageTaxRate <= 1e18, "Invalid tax rate")` to match
the existing `percentageStaked` guard and prevent silent acceptance
of out-of-range values.
- Expand contract-level NatSpec with a @dev note clarifying this is an
equivalence proof only: it intentionally exposes `isBullMarket` alone
and is not a deployable upgrade (full optimizer interface missing).
All 15 Foundry tests pass (15 unit + fuzz).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>