// 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"); } } }