{
  "date": "2026-03-22",
  "issue": 517,
  "title": "Adversary parasitic LP extracts 29% from holder — all recenters fail",
  "scenario": "staker-vs-holder",
  "status": "fixed",
  "root_cause": {
    "summary": "PRICE_STABILITY_INTERVAL (300s) too long relative to MIN_RECENTER_INTERVAL (60s)",
    "detail": "After a large trade moving the tick >1000 positions, the 5-minute TWAP average lagged behind the current price by hundreds of ticks, far exceeding MAX_TICK_DEVIATION (50). Recenter reverted with 'price deviated from oracle' for ~285s after each trade, creating a window where the LM could not reposition. The adversary's parasitic LP captured fees during this unprotected window.",
    "revert_reasons": {
      "after_adversary_setup": "price deviated from oracle",
      "after_holder_buy": "price deviated from oracle",
      "after_adversary_attack": "price deviated from oracle",
      "after_holder_sell": "amplitude not reached"
    },
    "johba_comment_confirmed": "Parasitic LP does not directly block recentering (V3 positions are independent). The revert is from the TWAP stability check, not from position interference."
  },
  "fix": {
    "file": "onchain/src/abstracts/PriceOracle.sol",
    "change": "PRICE_STABILITY_INTERVAL reduced from 300 to 30 seconds",
    "rationale": "30s still prevents same-block manipulation (Ethereum mainnet ~12s block time) while ensuring TWAP converges well within the 60s cooldown. After the fix, recenter succeeds within 61s of any trade.",
    "security_impact": "Manipulation window reduced from 5 min to 30s. Attacker must hold manipulated price for 30+ seconds (2.5 blocks) before recenter accepts it. Combined with 60s cooldown, total manipulation window is <60s."
  },
  "tests_added": [
    "testRecenterAfterLargeBuy_TWAPConverges — verifies recenter works after 5 ETH buy + 61s wait",
    "testRecenterRejectsSameBlockManipulation — verifies TWAP check still blocks <30s manipulation",
    "testAdversarialLP_HolderProtected — full parasitic LP scenario, holder loss < 5%"
  ],
  "test_results": {
    "total": 256,
    "passed": 255,
    "failed": 1,
    "skipped": 0,
    "pre_existing_failure": "FitnessEvaluator.t.sol::testBatchEvaluate (requires FITNESS_MANIFEST_DIR env var)"
  }
}
