The KRAIKEN protocol contracts are well-structured with clear separation of concerns. No critical exploitable vulnerabilities were found that would block mainnet launch. Two medium-severity issues should be addressed before deployment: a TWAP calculation bug in the PriceOracle fallback path, and the lack of access control on one-time setter functions during deployment. Several low-severity and informational findings are documented below.
The previously identified Floor Ratchet Extraction Attack (branch `fix/floor-ratchet`) remains the highest-priority issue and is tracked separately.
averageTick = int24(tickCumulativeDiff / int56(int32(PRICE_STABILITY_INTERVAL))); // divides by 300, not 60000
```
**Issue:** The fallback path observes a 60,000-second window but divides by 300 (the original 5-minute interval). This produces an `averageTick` that is 200x the actual TWAP value.
**Impact:** When the fallback triggers (new pool with <5minofobservationhistory),`_isPriceStable()`alwaysreturns`false`,making`recenter()`permanentlyblockeduntilenoughobservationsaccumulate.Thisisa**liveness issue**fornewlydeployedpools—theprotocolcannotperformitsfirstrecenteruntilthepoolhasaccumulatedsufficientTWAPhistory.
**Original concern:** When `recenterAccess == address(0)`, anyone could call `recenter()` as long as the TWAP check passed, with no cooldown or rate limiting.
**Resolution:** The `recenterAccess` role and its associated setter/revoker functions (`setRecenterAccess`, `revokeRecenterAccess`) have been **removed** from the contract. `recenter()` is now unconditionally permissionless. In their place, a `MIN_RECENTER_INTERVAL = 60` second cooldown is enforced on every call path with no bypass:
**Assessment:** The 60-second cooldown directly addresses the original gas-griefing and VWAP-frequency concerns. Combined with the amplitude check (>400 ticks from center) and TWAP oracle guard (5-min, 50-tick tolerance), the attack surface is materially reduced. No further action required.
**Issue:** If `totalSupply() == stakingPoolBalance` (staking pool holds 100% of tokens), the denominator is zero.
**Impact:** `recenter()` reverts when trying to mint KRK for new positions.
**Mitigating factors:** In practice, the LM always holds significant KRK in positions, making `totalSupply() > stakingPoolBalance` invariant. The only way to reach this state would be for the LM to burn all its tokens AND for all remaining supply to be in the staking pool — which would require zero active positions (impossible mid-operation since `recenter` burns then mints).
---
### L-2: OptimizerV3 Integer Truncation at Bull/Bear Boundary
**Issue:** 91.9% staked truncates to `91`, triggering bear mode even though staking is close to the 92% threshold.
**Impact:** The bull/bear boundary has ~1% hysteresis due to truncation. This is actually beneficial — it makes the boundary slightly harder to reach, adding a buffer against oscillation.
---
### I-1: Missing `Recentered` Event
**Severity:** Informational
**File:** `src/LiquidityManager.sol:121`
`recenter()` performs the most critical protocol operation but emits no event. The `EthScarcity`/`EthAbundance` events exist in `ThreePositionStrategy` but only fire during floor tick computation. A top-level `Recentered(int24 tick, bool isUp)` event would improve monitoring and indexing.
---
### I-2: VWAP Directional Recording Is Sound But Has Known Limitations
**Severity:** Informational
**File:** `src/LiquidityManager.sol:146-158`
The directional VWAP recording (only record on ETH inflow / buys) is a deliberate design choice to prevent sell-side VWAP dilution. An attacker could theoretically buy to inflate VWAP, then sell without VWAP recording. However:
- Buying costs real ETH (not free to manipulate)
- VWAP is volume-weighted, so one-off manipulation is diluted by historical volume
- The VWAP mirror defense naturally increases floor distance during sell pressure
This is acceptable behavior by design.
---
### I-3: ThreePositionStrategy Floor Position at Zero Outstanding Supply
When `outstandingSupply` reaches 0 after subtracting `pulledKraiken` and `discoveryAmount`, the scarcity tick goes to `MAX_TICK` (extreme KRK-cheap). This is **correct by design** — when there's no outstanding supply to protect, the floor should be as far as possible from current price, locking ETH in a position that's virtually unreachable.
**`recenter()`:** No reentrancy risk. The function:
1. Reads pool state (slot0)
2. Burns all positions via `pool.burn()` (Uniswap V3 pools are not reentrant)
3. Collects tokens via `pool.collect()`
4. Transfers fees to `feeDestination`
5. Mints new positions via `pool.mint()`
The `uniswapV3MintCallback` is validated via `CallbackValidation.verifyCallback(factory, poolKey)` which ensures only the canonical pool can trigger it. The callback mints KRK tokens and wraps ETH — neither of which creates reentrant paths back to `recenter()`.
**`_scrapePositions()`:** Token transfers (`IERC20.transfer`) to `feeDestination` could theoretically trigger a callback if `feeDestination` is a contract. However, WETH and KRK transfers do not have callback hooks (no ERC-777 or similar), so this is safe.
---
## Known Issues (Tracked Separately)
### Floor Ratchet Extraction Attack
**Branch:** `fix/floor-ratchet`
**Severity:** High (exploitable in 2000+ trade scenarios)
**Summary:** Rapid recenters ratchet the floor position toward current price while packing ETH into it, enabling extraction through coordinated buy-crash-recenter-sell cycles. See `MEMORY.md` deep fuzzing results for full analysis.
---
## Conclusion
The protocol is ready for mainnet deployment with the following pre-launch actions:
3.~~**Mitigate M-3**~~ — **Resolved:**`recenterAccess` was removed; `MIN_RECENTER_INTERVAL` (60s) cooldown is now enforced unconditionally on all `recenter()` calls