fix: Floor Ratchet 2000-trade oscillation needs a dedicated full-sequence red-team run (#1082)

- Expand floor-ratchet-oscillation.jsonl to 2000 buy→recenter cycles
  (10 rounds × 200 cycles at 5 ETH/buy with stake/unstake/sell phases)
- Fix AttackRunner buy_recenter_loop: add vm.warp/vm.roll for recenter
  cooldown bypass and TWAP convergence; use single-signer broadcast
- Fix AttackRunner mine op: advance timestamp alongside block number
- Replace pending 2026-03-22 evidence with completed 2026-03-23 run
- Result: INCREASED (+1230 bps). TWAP oracle blocked 99.9% of recenters.
  Floor ratchet risk from #630 is defeated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
johba 2026-03-23 09:12:00 +00:00
parent 144d6a2f7f
commit 69ba4fd44e
4 changed files with 94 additions and 56 deletions

View file

@ -300,6 +300,7 @@ contract AttackRunner is Script {
} else if (_eq(op, "mine")) {
uint256 blocks = vm.parseJsonUint(line, ".blocks");
vm.roll(block.number + blocks);
vm.warp(block.timestamp + blocks * 2);
} else {
console.log(string.concat("AttackRunner: unknown op '", op, "' -- skipping (check attack file for typos)"));
}
@ -336,7 +337,13 @@ contract AttackRunner is Script {
uint256 amount = vm.parseUint(vm.parseJsonString(line, ".amount"));
for (uint256 i = 0; i < count; i++) {
// Buy WETHKRK.
// Advance time past recenter cooldown (60s) and TWAP stability
// window (30s) so the oracle incorporates the previous buy's price.
vm.warp(block.timestamp + 61);
vm.roll(block.number + 1);
// Buy and recenter in one broadcast (recenter is public any address can call).
// Using a single signer avoids multi-key broadcast issues in forge.
vm.startBroadcast(ADV_PK);
ISwapRouter02(swapRouter).exactInputSingle(
ISwapRouter02.ExactInputSingleParams({
@ -349,16 +356,12 @@ contract AttackRunner is Script {
sqrtPriceLimitX96: 0
})
);
vm.stopBroadcast();
// Recenter.
vm.startBroadcast(RECENTER_PK);
try ILM(lmAddr).recenter() returns (bool isUp) {
_lastRecenterIsUp = isUp;
_hasRecentered = true;
_logSnapshot(_seq++);
} catch {
console.log("recenter: skipped (amplitude not reached)");
// Amplitude not reached or price still deviating continue loop.
}
vm.stopBroadcast();
}