Commit graph

289 commits

Author SHA1 Message Date
openhands
9b157883b4 fix: AttackRunner.s.sol: V3_FACTORY still hardcoded to Base mainnet (#953)
Make V3_FACTORY injectable via vm.envOr("V3_FACTORY", DEFAULT_V3_FACTORY),
preserving the Base mainnet address as the default for existing fork runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 10:40:33 +00:00
openhands
ee867b256e fix: add symmetric InvalidAddress guard to setLiquidityManager (#935)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 09:45:43 +00:00
openhands
f3238a9685 fix: Kraiken.setStakingPool() allows stakingPool == liquidityManager with no guard (#935)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 08:09:43 +00:00
openhands
13f406b5a9 fix: red-team.sh and AttackRunner.s.sol still use Base mainnet addresses (#939)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 07:33:54 +00:00
openhands
69d161aef1 fix: DeployLocal.sol v3Factory still uses Base Sepolia address (#714)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 00:31:48 +00:00
openhands
e4225b364b fix: Double-subtraction if stakingPoolAddr == feeDestination (#911) 2026-03-17 20:49:46 +00:00
openhands
ad7a45a40e fix: onchain/AGENTS.md:71 missing conditional guard on feeDestination/stakingPool exclusion (#891) 2026-03-17 11:37:12 +00:00
openhands
a8e186caac fix: onchain/analysis/PARAMETER_SEARCH_RESULTS.md shows stale unconditional pseudocode (#893)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 09:47:37 +00:00
openhands
fb83c70d23 fix: chore: fetch and cache Uniswap V3 replay datasets for evolution backtesting (#883)
- Add fetch-datasets.sh wrapper that fetches HIGHER/WETH, DEGEN/WETH,
  and TOSHI/WETH 30-day event caches via fetch-events.ts; reads
  INFURA_API_KEY from env and fails with a helpful error if unset
- Update .gitignore from cache/ (whole dir) to cache/*.jsonl so the
  pattern is precise to the generated data files; cache/ is already
  covered by the repo-root .gitignore via its own cache/ rule

JSONL cache files are gitignored and must be generated locally by
running ./fetch-datasets.sh with INFURA_API_KEY set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 15:50:19 +00:00
openhands
10c90e4c50 fix: address AI review findings on SECURITY_REVIEW.md and deployment.md (#838)
- M-2: update body to show current deployer-only setFeeDestination()
  implementation and conditional locking; mark as partially resolved;
  downgrade severity from Medium to Low; update conclusion entry
- I-1: mark as resolved — Recentered event declared at line 66 and
  emitted at line 224 of LiquidityManager.sol
- I-2: correct VWAP direction (records on sells/ETH outflow, not buys);
  update stale line reference from 146-158 to 177-191
- deployment.md §6.5: replace vague 'assess severity' step 1 with
  concrete action (upgrade optimizer to bear defaults via §6.2)
- deployment.md §8 timeline: remove stale 'Set recenter access' row;
  update 'First recenter' dependency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 15:15:33 +00:00
openhands
9b75817300 fix: SECURITY_REVIEW.md references obsolete recenterAccess pattern (#838)
- Update M-3 finding: recenterAccess was removed; MIN_RECENTER_INTERVAL
  (60s) cooldown now enforced unconditionally — downgrade severity to
  Informational (resolved)
- Update Access Control Summary: remove recenterAccess rows, reflect
  permissionless recenter() with cooldown
- Update Conclusion: mark M-3 as resolved
- Fix stale M-1 impact note that mentioned recenterAccess as a workaround
- deployment.md: remove Section 3.2 "Set Recenter Access" (setRecenterAccess
  no longer exists); update 3.3 first-recenter comment
- deployment.md: replace recenterAccess() verification call with
  lastRecenterTime() check
- deployment.md §6.1: rewrite Pause Recentering note — no access-control
  switch exists, cooldown is the only rate limiter
- deployment.md §6.5: remove stale setRecenterAccess(0xdEaD) instruction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 14:43:37 +00:00
openhands
fd912a2a69 fix: AttackRunner.s.sol NPM_ADDR last byte is 0xF1 but scripts use 0xF3 (#807)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:50:28 +00:00
openhands
aa274fd8ed fix: address review findings for anchorWidth guard (#817)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 22:04:13 +00:00
openhands
a21cf398bf fix: Unclamped anchorWidth can overflow tick range — no upper-bound guard after MAX_ANCHOR_WIDTH removal (#783) (#817)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 21:34:33 +00:00
openhands
9a309634ed chore: add planner watermarks to all AGENTS.md files 2026-03-15 16:42:45 +00:00
openhands
d3917c551f fix: ThreePositionStrategy class comment still advertises 1-100% anchor width (#786)
- Fix class-level NatSpec: use accurate wording (width computed from
  anchorWidth param provided by Optimizer) instead of imprecise
  LiquidityManager attribution
- Fix inline comment in _setAnchorPosition (same stale 1-100% claim)
- Update PRODUCT-TRUTH.md and ARCHITECTURE.md which had the same
  incorrect 1-100% range claim

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 08:51:12 +00:00
openhands
f5fdd329c4 fix: ThreePositionStrategy class comment still advertises 1-100% anchor width (#786)
Remove the misleading "(1-100% width)" range claim from the ANCHOR NatSpec.
Anchor width enforcement lives in LiquidityManager, not this abstract, so
the comment is replaced with a note pointing to where enforcement actually occurs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 08:20:13 +00:00
openhands
cef4c3038b fix: fix: FitnessEvaluator.t.sol broken on Base mainnet fork (#780)
Address review feedback:
- Add comment on FEE_DEST explaining why it differs from DeployBaseMainnet.sol:
  on a Base mainnet fork, 0xf6a3...D9011 has contract code which triggers
  feeDestinationLocked=true; keccak-derived address is a guaranteed EOA
- Expand bytecodes.txt EOF guard with a pipeline-mismatch warning log
- Fix STATE.md entry to use imperative verb form per project convention

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 02:13:04 +00:00
openhands
ef3f6945f2 fix: fix: FitnessEvaluator.t.sol broken on Base mainnet fork (#780)
- Replace Base Sepolia addresses with Base mainnet:
  - V3_FACTORY: 0x33128a8fC17869897dcE68Ed026d694621f6FDfD
  - SWAP_ROUTER: 0x2626664c2603336E57B271c5C0b26F421741e481
  - NPM_ADDR: 0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1
- Fix FEE_DEST to keccak-derived EOA (0x8A9145E1...9383) to avoid
  feeDestination lock triggered by contract code at the old address
- Add vm.warp(block.timestamp + 600) alongside vm.roll in bootstrap
  retry loop so _isPriceStable() TWAP check accumulates 300s+ history
- Guard vm.readLine(bytecodesFile) with empty-string break to prevent
  vm.parseBytes("") revert on trailing EOF line

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 01:50:54 +00:00
openhands
8c50578be1 fix: update stale comments after CALCULATE_PARAMS_GAS_LIMIT bump to 500k
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 00:28:12 +00:00
openhands
87a088bc66 fix: fix: increase CALCULATE_PARAMS_GAS_LIMIT from 200k to 500k (#782)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 00:00:56 +00:00
openhands
ac4aa745f6 fix: fix: remove MAX_ANCHOR_WIDTH clamp in ThreePositionStrategy (#783)
Remove the MAX_ANCHOR_WIDTH=100 constant and the corresponding clamp on
anchorWidth in LiquidityManager.recenter(). The optimizer is now free to
choose any anchor width; evolution run 7 immediately exploited AW=153.

Update IOptimizer.sol NatSpec to reflect no clamping. Update the
testAnchorWidthAbove100IsClamped test to testAnchorWidthAbove100IsNotClamped,
asserting the tick range matches the full AW=150 width.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 23:21:30 +00:00
openhands
89580171b7 fix: DeployLocal.sol feeDest 0xf6a3... may have code on Base Sepolia fork (#760) 2026-03-14 20:21:32 +00:00
openhands
4d16a51650 fix: address review feedback on mainnet-bootstrap runbook (#728)
- docs/mainnet-bootstrap.md: fix Step 4c to use SwapRouter02 7-field
  struct (no deadline field); the 8-field ABI was for SwapRouter v1 but
  the address is SwapRouter02
- docs/mainnet-bootstrap.md: correct Step 1 to no longer falsely claim
  that pre-bootstrap transactions succeed when Forge aborts on simulation
  failure; Step 1 now reflects the try/catch behaviour added below
- docs/mainnet-bootstrap.md: Step 6 drops --private-key flag (Foundry
  ignores it when vm.startBroadcast(privateKey) is called internally)
  and documents that the .secret seed-phrase file must be present
- docs/mainnet-bootstrap.md: remove no-op `export LM_ADDRESS="$LM_ADDRESS"`
- docs/mainnet-bootstrap.md: cite exact line range (101-145) in
  Troubleshooting workaround instead of informal marker description
- onchain/script/DeployBase.sol: wrap liquidityManager.recenter() and
  seed buy in try/catch so a fresh-pool TWAP revert skips the inline
  bootstrap with a warning rather than aborting the entire simulation
- onchain/script/DeployBase.sol: fix --fork-url to --rpc-url in the
  post-deploy console.log hint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 18:35:48 +00:00
openhands
023c661ee7 fix: No mainnet VWAP bootstrap runbook (#728)
Add docs/mainnet-bootstrap.md with the full two-phase bootstrap
sequence: pool init, 300 s TWAP warm-up wait, first recenter + seed
buy (exact cast commands), 60 s cooldown wait, second recenter via
BootstrapVWAPPhase2.s.sol, and verification/troubleshooting steps.

Update the inline bootstrap comment in DeployBase.sol to warn that the
attempt always reverts on a fresh pool and direct operators to the new
runbook.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 18:04:11 +00:00
openhands
8826c0b812 ci: retrigger (mirror available) 2026-03-14 13:31:23 +00:00
openhands
af8bd07c7d feat: add red-team discovered IL crystallization attack (31.9 ETH optimal)
Single-cycle attack extracts 21.3 ETH (2.13%) from 1000 ETH LM:
  buy 31.9 ETH → recenter → sell all KRK

Key finding: thin pre-recenter positions allow massive price impact,
recenter rebuilds deep positions at manipulated price, sell through
deep positions recovers most ETH. IL crystallized during recenter.

This is the optimal single-buy amount — 31.95+ hits max tick,
<31 ETH extracts proportionally less.
2026-03-14 13:31:23 +00:00
openhands
dbf78de793 fix: bootstrap + red-team on forked networks
Bootstrap fixes:
- Idempotency check: skip if Kraiken already deployed on Anvil
- anvil_setCode to strip ERC-4337 code from deployer + feeDest
- DeployLocal.sol: feeDest derived from keccak256('harb.local.feeDest')

Red-team fixes:
- New bootstrap-light.sh: Anvil-only, ~30s deploy
- red-team.sh uses bootstrap-light instead of full docker compose
- anvil_setBalance for feeDest before impersonation
- forge --color never, path resolution, docker chown

Address fixes (all Base mainnet, in both FitnessEvaluator + AttackRunner):
- V3_FACTORY: 0x33128a8fC17869897dcE68Ed026d694621f6FDfD
- SWAP_ROUTER: 0x2626664c2603336E57B271c5C0b26F421741e481
- NPM_ADDR: 0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1
2026-03-14 13:31:23 +00:00
johba
6ff8282a7e Merge pull request 'fix: Remove recenterAccess — make recenter() public with TWAP enforcement (#706)' (#713) from fix/issue-706 into master 2026-03-14 10:48:59 +01:00
openhands
0d3aee15b4 fix: address AI review findings for #706 recenterAccess removal
- DeployBase.sol: remove broken inline second recenter() (would always
  revert with 'recenter cooldown' in same Forge broadcast); replace with
  operator instructions to run the new BootstrapVWAPPhase2.s.sol script
  at least 60 s after deployment
- BootstrapVWAPPhase2.s.sol: new script for the second VWAP bootstrap
  recenter on Base mainnet deployments
- StrategyExecutor.sol: update stale docstring that still described the
  removed recenterAccess bypass; reflect permissionless model with vm.warp
- TestBase.sol: remove vestigial recenterCaller parameter from all four
  setupEnvironment* functions (parameter was silently ignored after
  setRecenterAccess was removed); update all callers across six test files
- bootstrap-common.sh: fix misleading retry recenter in
  seed_application_state() — add evm_increaseTime 61 before evm_mine so
  the recenter cooldown actually clears and the retry can succeed

All 210 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 09:15:48 +00:00
openhands
bc2ed50451 fix: V3_FACTORY address lacks a source comment (#730)
Add inline Basescan URL comment identifying V3_FACTORY as the Uniswap V3
Factory on Base mainnet, consistent with the existing comment style used
for NPM_ADDR in both files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 06:57:02 +00:00
openhands
2c849ec456 fix: calculateParams in OptimizerV3Push3 has no NatSpec after this PR (#735)
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>
2026-03-14 06:20:37 +00:00
openhands
6978d1399f fix: OptimizerV3 / OptimizerV3Push3 not explicitly typed against IOptimizer (#661)
- 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>
2026-03-14 05:08:32 +00:00
openhands
cbab4c36da fix: NPM_ADDR may be Base Sepolia address in both files (#686)
Replace 0x27F971cb582BF9E50F397e4d29a5C7A34f11faA2 (Base Sepolia
NonfungiblePositionManager) with the correct Base mainnet address
0x03a520B32c04bf3beef7BEb72E919cF822Ed34F3 in all four files that
referenced it, and add an inline comment citing the chain and source.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 02:22:51 +00:00
openhands
f3ddec9427 fix: Other clamped params lack named constants (#703)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 01:40:41 +00:00
openhands
02b055ceb9 fix: move VWAP bootstrap from forge script to bootstrap-common.sh
vm.warp in forge script --broadcast only affects the local simulation
phase, not the actual Anvil node.  The pool.observe([300,0]) call in
recenter() therefore reverted with OLD when Forge pre-flighted the
broadcast transactions on Anvil.

Fix:
- Remove the vm.warp + 2-recenter + SeedSwapper VWAP bootstrap from
  DeployLocal.sol (only contract deployment now, simpler and reliable).
- Add bootstrap_vwap() to bootstrap-common.sh that uses Anvil RPC
  evm_increaseTime + evm_mine to advance chain time before each recenter,
  then executes a 0.5 ETH WETH->KRK seed swap between them.
- Call bootstrap_vwap() before fund_liquidity_manager() in both
  containers/bootstrap.sh and ci-bootstrap.sh so the LM is seeded with
  thin positions (1 ETH) during bootstrap, ensuring the 0.5 ETH swap
  moves the price >400 ticks (amplitude gate).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 23:28:52 +00:00
openhands
df2f0a87e5 fix: track ts explicitly in DeployLocal bootstrap to avoid Forge block.timestamp reset
Forge resets block.timestamp to its pre-warp value after each state-changing
call (e.g. recenter()). The second vm.warp(block.timestamp + 301) in the VWAP
bootstrap was therefore warping to the same timestamp as the first warp, so
lastRecenterTime + 60 > block.timestamp and the second recenter() reverted
with "recenter cooldown".

Fix: store ts = block.timestamp + 301 before the first warp and increment it
explicitly (ts += 301) before the second warp, mirroring the same pattern
applied to VWAPFloorProtection.t.sol and SupplyCorruption.t.sol.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 22:45:22 +00:00
openhands
1a410a30b7 fix: Remove recenterAccess — make recenter() public with TWAP enforcement (#706) 2026-03-13 22:32:53 +00:00
openhands
39c25fa330 fix: LiquidityManager silently clamps anchorWidth to 100, undocumented upper bound (#689)
- Extract magic number into named constant MAX_ANCHOR_WIDTH = 100 in LiquidityManager.sol
- Document effective ceiling in IOptimizer.sol natspec for anchorWidth return value
- Add testAnchorWidthAbove100IsClamped in LiquidityManager.t.sol asserting that
  optimizer-returned anchorWidth=150 is silently clamped to 100 (not rejected)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 18:14:37 +00:00
openhands
defa1bfb6c fix: fix: Fitness metric should measure ETH only, not token value (#670)
Replace _positionEthValue() with _positionEthOnly() in FitnessEvaluator.t.sol.
The new function returns only the WETH component of each position (amount0 if
token0isWeth, else amount1), ignoring KRK token value entirely. This prevents
evolution from gaming the fitness metric by inflating KRK price through position
placement — the score now reflects actual ETH reserves only.

Also removes the now-unused FullMath import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 09:11:31 +00:00
openhands
d01c561028 fix: No formal IOptimizer interface for getLiquidityParams ABI (#556)
Add IOptimizer interface with getLiquidityParams() signature to IOptimizer.sol
so upgrade-compatibility is explicit and static analysis can catch ABI mismatches.
Update LiquidityManager to hold optimizer as IOptimizer instead of concrete Optimizer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 07:20:42 +00:00
johba
8064623a54 fix: feat: Push3 input redesign — normalized indicators instead of raw protocol values (#635) (#649)
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>
2026-03-13 07:53:46 +01:00
openhands
c87064dc6c fix: feat: Push3 default outputs — crash/no-output falls back to bear strategy (#634)
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>
2026-03-13 03:47:49 +00:00
johba
8e4bd905ac Merge pull request 'fix: fix: Bootstrap VWAP with seed trade during deployment (#567) (#567)' (#633) from fix/issue-567 into master 2026-03-13 03:17:30 +01:00
openhands
5d369cfab6 fix: address review findings for gas-limit fitness pressure (#637)
- Optimizer.sol: move CALCULATE_PARAMS_GAS_LIMIT constant to top of
  contract (after error declaration) to avoid mid-contract placement.
  Expand natspec with EIP-150 63/64 note: callers need ~203 175 gas to
  deliver the full 200 000 budget to the inner staticcall.

- Optimizer.sol: add ret.length < 128 guard before abi.decode in
  getLiquidityParams(). Malformed return data (truncated / wrong ABI)
  from an evolved program now falls back to _bearDefaults() instead of
  propagating an unhandled revert. The 128-byte minimum is the ABI
  encoding of (uint256, uint256, uint24, uint256) — four 32-byte slots.

- Optimizer.sol: add cross-reference comment to _bearDefaults() noting
  that its values must stay in sync with LiquidityManager.recenter()'s
  catch block to prevent silent divergence.

- FitnessEvaluator.t.sol: add CALCULATE_PARAMS_GAS_LIMIT mirror constant
  (must match Optimizer.sol). Disqualify candidates whose measured gas
  exceeds the production cap with fitness=0 and error="gas_over_limit"
  — prevents the pipeline from selecting programs that are functionally
  dead on-chain (would always produce bear defaults in production).

- batch-eval.sh: update output format comment to document the gas_used
  field and over-gas-limit error object added by this feature.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 01:05:37 +00:00
openhands
c9c0ce5e95 fix: feat: Push3 evolution — gas limit as fitness pressure (#637)
- Optimizer.getLiquidityParams() now forwards calculateParams through a
  staticcall capped at 200 000 gas. Programs that exceed the budget or
  revert fall back to bear defaults (CI=0, AS=30%, AW=100, DD=0.3e18),
  so a bloated evolved optimizer can never OOG-revert inside recenter().

- FitnessEvaluator.t.sol measures gas used by calculateParams against
  fixed representative inputs (50% staked, 5% avg tax) after each
  bootstrap. A soft penalty of GAS_PENALTY_FACTOR (1e13 wei/gas) is
  subtracted from total fitness before the JSON score line is emitted.
  Leaner programs win ties; gas_used is included in the output for
  observability. At ~15k gas (current seed) the penalty is ~1.5e17 wei;
  at the 200k hard cap boundary it reaches ~2e18 wei.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 00:25:49 +00:00
openhands
38bc0f7057 fix: address AI review findings on VWAP bootstrap PR
SPDX license:
- Restore GPL-3.0-or-later SPDX header to DeployBase.sol (removed by
  the em-dash sed fix in an earlier commit).

SeedSwapper deduplication:
- Extract SeedSwapper into onchain/script/DeployCommon.sol — a single
  canonical definition shared by both deploy scripts.  This eliminates
  duplicate Foundry artifacts (previously both DeployLocal.sol and
  DeployBase.sol produced a SeedSwapper artifact, causing ambiguity for
  verification and coverage tools).
- Remove inline SeedSwapper and redundant IWETH9 import from
  DeployLocal.sol and DeployBase.sol; add `import "./DeployCommon.sol"`.

SeedSwapper hardening (in DeployCommon.sol):
- Replace magic-literal price sentinels with named constants
  SQRT_PRICE_LIMIT_MIN / SQRT_PRICE_LIMIT_MAX.
- Wrap both weth.transfer() calls with require() so a non-standard
  WETH9 false-return is caught rather than silently ignored.
- Add post-swap WETH sweep in executeSeedBuy(): if the price limit is
  reached before the full input is spent, the residual WETH balance is
  returned to `recipient` instead of being stranded in the contract.

bootstrap-common.sh:
- Normalise cumulativeVolume output through `cast to-dec` before the
  string comparison, guarding against a future change in cast output
  format (decimal vs hex).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 00:12:39 +00:00
openhands
3e9c3e6533 fix: increase SEED_SWAP_ETH to overcome amplitude check in bootstrap
The 0.01 ETH seed swap only moved the tick 127 ticks from the start
and 37 ticks from the ANCHOR center — far below the 400-tick minimum
amplitude (2 × TICK_SPACING).  As a result, the second recenter()
always reverted with "amplitude not reached", preventing VWAP bootstrap.

Root cause: SEED_SWAP_ETH was 1 % of SEED_LM_ETH.  The ANCHOR
position holds ~25 % of SEED_LM_ETH as WETH across ~7 200 ticks, so
consuming half of that WETH (≈0.125 ETH) is already enough to move
the price 3 600 ticks past centre.

Fix: raise SEED_SWAP_ETH from 0.01 ether to 0.5 ether (50 % of
SEED_LM_ETH), giving a 4× margin over the minimum required.  Verified
against a Base-Sepolia fork at block 20 000 000 (same environment as
CI): VWAP is now bootstrapped and cumulativeVolume > 0 after deployment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:34:28 +00:00
openhands
c05b20d640 fix: fix: Bootstrap VWAP with seed trade during deployment (#567) (#567)
Deploy scripts (DeployLocal.sol and DeployBase.sol) now execute a
seed buy + double-recenter sequence before handing control to users:

1. Temporarily grant deployer recenterAccess (via self as feeDestination)
2. Fund LM with a small amount and call recenter() -> places thin positions
3. SeedSwapper executes a small buy, generating a non-zero WETH fee
4. Second recenter() hits the cumulativeVolume==0 bootstrap path with
   ethFee>0 -> _recordVolumeAndPrice fires -> cumulativeVolume>0
5. Revoke recenterAccess and restore the real feeDestination

After deployment, cumulativeVolume>0, so the bootstrap path is
unreachable by external users and cannot be front-run by an attacker
inflating the initial VWAP anchor with a whale buy.

Also adds:
- tools/deploy-optimizer.sh: verification step checks cumulativeVolume>0
  after a fresh local deployment
- test_vwapBootstrappedBySeedTrade() in VWAPFloorProtection.t.sol:
  confirms the deploy sequence (recenter + buy + recenter) leaves
  cumulativeVolume>0 and getVWAP()>0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 21:15:35 +00:00
johba
b456bc75fd Merge pull request 'fix: LM recenter() return semantics undocumented (#570)' (#626) from fix/issue-570 into master 2026-03-12 21:53:34 +01:00