harb/onchain/test/SupplyCorruption.t.sol
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

150 lines
5.3 KiB
Solidity

// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;
/**
* @title Supply Corruption Test
* @notice Regression test for issue #98: KRK Token Supply Corruption During Recenter Operations
* @dev Verifies that recenter() correctly mints tokens when liquidity increases, preventing deflation
*/
import { Kraiken } from "../src/Kraiken.sol";
import "../src/interfaces/IWETH9.sol";
import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
import "forge-std/Test.sol";
import { LiquidityManager } from "../src/LiquidityManager.sol";
import { Optimizer } from "../src/Optimizer.sol";
import { Stake } from "../src/Stake.sol";
import { TestEnvironment } from "./helpers/TestBase.sol";
import { UniSwapHelper } from "./helpers/UniswapTestBase.sol";
contract SupplyCorruptionTest is UniSwapHelper {
address constant RECENTER_CALLER = address(0x7777);
address feeDestination = makeAddr("fees");
IUniswapV3Factory factory;
Stake stake;
LiquidityManager lm;
Optimizer optimizer;
TestEnvironment testEnv;
function setUp() public {
testEnv = new TestEnvironment(feeDestination);
(
IUniswapV3Factory _factory,
IUniswapV3Pool _pool,
IWETH9 _weth,
Kraiken _harberg,
Stake _stake,
LiquidityManager _lm,
Optimizer _optimizer,
bool _token0isWeth
) = testEnv.setupEnvironment(false);
factory = _factory;
pool = _pool;
weth = _weth;
harberg = _harberg;
stake = _stake;
lm = _lm;
optimizer = _optimizer;
token0isWeth = _token0isWeth;
}
/**
* @notice Test that recenter does not cause supply corruption
* @dev Reproduces the bug scenario: after buying tokens and calling recenter,
* totalSupply should increase or stay the same, never decrease
*/
function testRecenterDoesNotCorruptSupply() public {
// Fund liquidity manager with ETH
vm.deal(address(lm), 10 ether);
// Initial recenter to set up positions
vm.prank(RECENTER_CALLER);
lm.recenter();
// Record initial state
uint256 initialTotalSupply = harberg.totalSupply();
(address liquidityManagerAddr, address stakingPool) = harberg.peripheryContracts();
uint256 initialStakingBalance = harberg.balanceOf(stakingPool);
assertGt(initialTotalSupply, 0, "Initial total supply should be positive");
console.log("Initial totalSupply:", initialTotalSupply);
console.log("Initial staking balance:", initialStakingBalance);
// Simulate user buying tokens (price movement)
vm.deal(account, 5 ether);
vm.prank(account);
weth.deposit{ value: 5 ether }();
// Perform swap to move price
performSwap(5 ether, true);
console.log("Performed 5 ETH swap to move price");
vm.warp(block.timestamp + 301); // TWAP catches up to post-swap price; cooldown passes
// Call recenter
vm.prank(RECENTER_CALLER);
lm.recenter();
// Check final state
uint256 finalTotalSupply = harberg.totalSupply();
uint256 finalStakingBalance = harberg.balanceOf(stakingPool);
console.log("Final totalSupply:", finalTotalSupply);
console.log("Final staking balance:", finalStakingBalance);
// CRITICAL ASSERTION: Total supply should not decrease
assertGe(finalTotalSupply, initialTotalSupply, "BUG #98: Total supply should not decrease during recenter");
// Staking pool ratio should be maintained (within 1% tolerance for rounding)
if (initialTotalSupply > 0) {
uint256 initialRatio = (initialStakingBalance * 10_000) / initialTotalSupply;
uint256 finalRatio = (finalStakingBalance * 10_000) / finalTotalSupply;
uint256 ratioDiff = finalRatio > initialRatio ? finalRatio - initialRatio : initialRatio - finalRatio;
assertLt(
ratioDiff,
100, // 1% tolerance (100 out of 10000)
"Staking pool ratio should be maintained"
);
}
}
/**
* @notice Test multiple recenter operations don't accumulate supply corruption
*/
function testMultipleRecentersPreserveSupply() public {
vm.deal(address(lm), 20 ether);
// Initial recenter
vm.prank(RECENTER_CALLER);
lm.recenter();
uint256 initialTotalSupply = harberg.totalSupply();
console.log("Initial supply:", initialTotalSupply);
// Perform multiple recenter cycles
uint256 ts = block.timestamp; // track time explicitly to avoid Forge block.timestamp reset
for (uint256 i = 0; i < 3; i++) {
// Swap to move price
vm.deal(account, 2 ether);
vm.prank(account);
weth.deposit{ value: 2 ether }();
performSwap(2 ether, true);
ts += 301; // TWAP catches up; cooldown passes
vm.warp(ts);
vm.prank(RECENTER_CALLER);
lm.recenter();
uint256 currentSupply = harberg.totalSupply();
console.log("Supply after recenter", i + 1, ":", currentSupply);
assertGe(currentSupply, initialTotalSupply, "Supply should not decrease across multiple recenters");
}
}
}