- 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>
150 lines
5.3 KiB
Solidity
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");
|
|
}
|
|
}
|
|
}
|