Commit graph

330 commits

Author SHA1 Message Date
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
johba
c3c262a719 Merge pull request 'fix: revm evaluator — UUPS bypass, deployedBytecode, graceful attack ops' (#629) from fix/revm-evaluator into master
Reviewed-on: https://codeberg.org/johba/harb/pulls/629
Reviewed-by: review_bot <review_bot@noreply.codeberg.org>
2026-03-12 21:43:53 +01:00
openhands
87bb5859e2 fix: revm evaluator — UUPS bypass, deployedBytecode, graceful attack ops
- Skip UUPS upgradeTo: etch + vm.store ERC1967 implementation slot directly
  (OptimizerV3Push3 is standalone, no UUPS inheritance needed for evolution)
- Use deployedBytecode (runtime) instead of bytecode (creation) for vm.etch
- Inject transpiled body into OptimizerV3.sol (has getLiquidityParams via Optimizer)
  instead of using standalone OptimizerV3Push3.sol
- Wrap buy/sell/stake/unstake in try/catch — attack ops should not abort the batch
- Add /tmp read to fs_permissions for batch-eval manifest files
- Bootstrap recenter returns bool instead of reverting (soft-fail per candidate)
2026-03-12 19:54:58 +00:00
openhands
b8f5ed9411 fix: Dead code branch: lastRecenterTick==0 with cumulativeVolume>0 (#568)
Remove unreachable else branch in VWAP recording logic. The branch was
only reachable if lastRecenterTick==0 and cumulativeVolume>0, which
requires tick==0 on the very first recenter — virtually impossible on a
live pool. Collapse else-if into else and delete the corresponding
testVWAPElseBranch test that exercised the path via vm.store.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 19:21:11 +00:00
openhands
54976184bc fix: correct isUp NatSpec and bootstrap log direction (#570)
Fix the @return NatSpec for recenter() isUp: the previous description
was wrong for the token0=WETH ordering (claimed tick above center, but
the actual check is currentTick < centerTick when token0isWeth). The
correct invariant is isUp=true ↔ KRK price in ETH rose (buy event /
net ETH inflow), regardless of token ordering.

Also address review nit: StrategyExecutor._logRecenter() now logs
'direction=BOOTSTRAP' instead of 'direction=DOWN' when no anchor
position existed before the recenter (aLiqPre==0), eliminating the
misleading directional label on the first recenter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 19:12:46 +00:00
openhands
cb6d6e2292 fix: LM recenter() return semantics undocumented (#570)
Add NatSpec to recenter() documenting that the function always reverts
on failure (never silently returns false), listing all four revert
conditions, and clarifying that both true/false return values represent
a successfully-executed recenter with the value indicating price
direction (up vs down relative to previous anchor centre).

Also fix StrategyExecutor.maybeRecenter() to capture the isUp return
value from lm.recenter() and include it in the log output, making
price direction visible in backtesting replays.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 18:21:27 +00:00
openhands
2da70bedfd fix: capitalInefficiency always 0 in OptimizerV3Push3 undocumented (#573) 2026-03-12 17:46:25 +00:00
openhands
b902b89e3b fix: address review findings — CREATE2 guard, transition test, docs
- LiquidityManager.setFeeDestination: add CREATE2 bypass guard — also
  blocks re-assignment when the current feeDestination has since acquired
  bytecode (was a plain address when set, contract deployed to it later)
- LiquidityManager.setFeeDestination: expand NatSpec to document the
  EOA-mutability trade-off and the CREATE2 guard explicitly
- Test: add testSetFeeDestinationEOAToContract_Locks covering the
  realistic EOA→contract transition (the primary lock-activation path)
- red-team.sh: add comment that DEPLOYER_PK is Anvil account-0 and must
  only be used against a local ephemeral Anvil instance
- ARCHITECTURE.md: document feeDestination conditional-lock semantics and
  contrast with Kraiken's strictly set-once liquidityManager/stakingPool

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:13:50 +00:00
openhands
512640226b fix: fix: Conditional lock on feeDestination — lock when set to contract (#580) (#580)
- Add `feeDestinationLocked` bool to LiquidityManager
- Replace one-shot setter with conditional trapdoor: EOAs may be set
  repeatedly, but setting a contract address locks permanently
- Remove `AddressAlreadySet` error (superseded by the new lock mechanic)
- Replace fragile SLOT7 storage hack in red-team.sh with a proper
  `setFeeDestination()` call using the deployer key
- Update tests: replace AddressAlreadySet test with three new tests
  covering EOA multi-set, contract lock, and locked revert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 16:13:44 +00:00
openhands
9bb223cf95 fix: Optimizer.sol also silently accepts negative mantissa inputs (#582)
Add require(mantissa >= 0) guards in calculateParams before the uint256()
casts on inputs[0] and inputs[1], preventing negative int256 values from
wrapping to huge uint256 numbers and corrupting liquidity calculations.
Add two regression tests covering the revert paths for both slots.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 15:41:39 +00:00