Check pos.creationTime == 0 before pos.owner != msg.sender so that
calling exitPosition on a non-existent position correctly reverts with
PositionNotFound instead of the misleading NoPermission(caller, 0x0).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
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>
OptimizerV3Push3 is an equivalence-proof contract with only isBullMarket().
It cannot serve as an ERC1967Proxy implementation because it has no initialize()
or getLiquidityParams(). The CI bootstrap was failing because the proxy
deployment reverted when calling initialize() on the Push3 implementation.
Switch deploy scripts to Optimizer.sol (the base UUPS contract) which has the
full interface required by ERC1967Proxy and LiquidityManager.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 11 new targeted tests in Stake.t.sol to cover all reachable
uncovered branches and the untested permitAndSnatch() function:
- testRevert_TaxRateOutOfBounds_InSnatch: taxRate >= TAX_RATES.length in snatch()
- testRevert_PositionNotFound_NonLastInLoop: PositionNotFound inside the multi-position loop
- testRevert_TaxTooLow_NonLastInLoop: TaxTooLow inside the multi-position loop
- testSnatch_ExitLastPosition: _exitPosition() path for last snatched position
- testRevert_ExceededAvailableStake: no available stake, no positions provided
- testRevert_TooMuchSnatch_AvailableExceedsNeed: post-exit excess stake check
- testRevert_PositionNotFound_InChangeTax: changeTax() on non-existent position
- testRevert_TaxTooLow_InChangeTax: changeTax() with same/lower tax rate
- testRevert_NoPermission_InExitPosition: exitPosition() by non-owner
- testRevert_PositionNotFound_InPayTax: payTax() on non-existent position
- testPermitAndSnatch: EIP-712 permit + snatch in one transaction
Coverage achieved:
Lines: 99.33% (148/149)
Statements: 99.40% (167/168)
Branches: 93.55% (29/31) — 2 unreachable dead-code branches remain
Functions: 100.00% (15/15)
The 2 uncovered branches are dead code: the require() failure in
_shrinkPosition (caller always guards sharesToTake < pos.share) and
the PositionNotFound guard in exitPosition() (unreachable because
owner and creationTime are always set/cleared together, so
pos.owner==msg.sender implies pos.creationTime!=0 for any live caller).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove redundant `node_modules/` from onchain/.gitignore — the root
.gitignore already has `**/node_modules/` which covers the entire tree.
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>
onchain/ uses Foundry for dependency management, not yarn/npm.
Adding yarn.lock, package-lock.json, and node_modules/ to .gitignore
prevents accidental commits of JS toolchain artifacts in future.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
yarn install was run during forge build troubleshooting; the generated
lock file was not intentional and is architecturally inconsistent with
the Foundry-only onchain/ toolchain. Also restores package-lock.json
to its pre-npm-install state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PROBLEM:
Recenter operations were burning ~137,866 KRK tokens instead of minting
them, causing severe deflation when inflation should occur. This was due
to the liquidity manager burning ALL collected tokens from old positions
and then minting tokens for new positions separately, causing asymmetric
supply adjustments to the staking pool.
ROOT CAUSE:
During recenter():
1. _scrapePositions() collected tokens from old positions and immediately
burned them ALL (+ proportional staking pool adjustment)
2. _setPositions() minted tokens for new positions (+ proportional
staking pool adjustment)
3. The burn and mint operations used DIFFERENT totalSupply values in
their proportion calculations, causing imbalanced adjustments
4. When old positions had more tokens than new positions needed, the net
result was deflation
WHY THIS HAPPENED:
When KRK price increases (users buying), the same liquidity depth
requires fewer KRK tokens. The old code would:
- Burn 120k KRK from old positions (+ 30k from staking pool)
- Mint 10k KRK for new positions (+ 2.5k to staking pool)
- Net: -137.5k KRK total supply (WRONG!)
FIX:
1. Modified uniswapV3MintCallback() to use existing KRK balance first
before minting new tokens
2. Removed burn() from _scrapePositions() - keep collected tokens
3. Removed burn() from end of recenter() - don't burn "excess"
4. Tokens held by LiquidityManager are already excluded from
outstandingSupply(), so they don't affect staking calculations
RESULT:
Now during recenter, only the NET difference is minted or used:
- Collect old positions into LiquidityManager balance
- Use that balance for new positions
- Only mint additional tokens if more are needed
- Keep any unused balance for future recenters
- No more asymmetric burn/mint causing supply corruption
VERIFICATION:
- All 107 existing tests pass
- Added 2 new regression tests in test/SupplyCorruption.t.sol
- testRecenterDoesNotCorruptSupply: verifies single recenter preserves supply
- testMultipleRecentersPreserveSupply: verifies no accumulation over time
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Integrate staking and exitPosition actions into fuzzing scenarios
- Add staking metrics (percentageStaked, avgTaxRate) to CSV output
- Implement snatching mechanism when stake pool reaches capacity
- Add configurable staking parameters (enable/disable, buy bias, staking bias)
- Support configurable number of trades per run
- Simplify to single trader account with proportional ETH funding
- Configure tax rates: 0-15 for initial stakes, 28 for snatching
- Clean up debug logging and verbose output
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add staking/unstaking actions to fuzzing scenarios (stake every 3rd trade)
- Implement snatching logic when stake pool reaches capacity (uses max tax rate)
- Add configurable parameters:
- buyBias: Control buy vs sell ratio (0-100%)
- stakingBias: Control stake vs unstake ratio (0-100%)
- tradesPerRun: Configure number of trades per scenario
- staking: Enable/disable staking entirely
- Simplify to single trading strategy (_executeRandomLargeTrades)
- Fix memory issues by recording only every 5th trade to CSV
- Track staking metrics (stakes attempted/succeeded, snatches attempted/succeeded)
- Update CLAUDE.md with new fuzzing parameters and usage examples
- Clean up old TODO files and unused code
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added BUY_BIAS environment variable (0-100%) to control trading direction
- Implemented biased trading logic in all strategies (Random, Whale, Volatile, etc.)
- Created run-improved-fuzzing.sh script with buy bias support
- Fixed memory issues in CSV generation by simplifying string concatenation
- Fixed console.log parameter issues in staking functions
- Updated run-recorded-fuzzing.sh to accept buybias parameter
- Testing shows up to 99% of authorized stake reached with 100% buy bias
Co-Authored-By: Claude <noreply@anthropic.com>