Merge pull request 'fix: Test coverage: Kraiken + VWAPTracker to 100% (#283)' (#303) from fix/issue-283 into master

This commit is contained in:
johba 2026-02-26 06:34:08 +01:00
commit ed24a166ab
2 changed files with 162 additions and 0 deletions

View file

@ -170,4 +170,89 @@ contract KraikenTest is Test {
assertEq(kraiken.balanceOf(stakingPool), expectedStakingPoolBalance, "Staking pool balance did not adjust correctly after burn.");
assertEq(kraiken.totalSupply(), expectedTotalSupply, "Total supply did not match expected after burn.");
}
// ========================================
// SETTER VALIDATION TESTS
// ========================================
function testSetLiquidityManagerZeroAddress() public {
Kraiken freshKraiken = new Kraiken("KRAIKEN", "KRK");
vm.expectRevert(Kraiken.ZeroAddressInSetter.selector);
freshKraiken.setLiquidityManager(address(0));
}
function testSetLiquidityManagerAlreadySet() public {
// liquidityManager is already set in setUp calling again must revert
vm.expectRevert(Kraiken.AddressAlreadySet.selector);
kraiken.setLiquidityManager(makeAddr("anotherLiquidityManager"));
}
function testSetLiquidityManagerOnlyDeployer() public {
Kraiken freshKraiken = new Kraiken("KRAIKEN", "KRK");
address nonDeployer = makeAddr("nonDeployer");
vm.prank(nonDeployer);
vm.expectRevert("only deployer");
freshKraiken.setLiquidityManager(makeAddr("someManager"));
}
function testSetStakingPoolZeroAddress() public {
Kraiken freshKraiken = new Kraiken("KRAIKEN", "KRK");
vm.expectRevert(Kraiken.ZeroAddressInSetter.selector);
freshKraiken.setStakingPool(address(0));
}
function testSetStakingPoolAlreadySet() public {
// stakingPool is already set in setUp calling again must revert
vm.expectRevert(Kraiken.AddressAlreadySet.selector);
kraiken.setStakingPool(makeAddr("anotherStakingPool"));
}
function testSetStakingPoolOnlyDeployer() public {
Kraiken freshKraiken = new Kraiken("KRAIKEN", "KRK");
address nonDeployer = makeAddr("nonDeployer");
vm.prank(nonDeployer);
vm.expectRevert("only deployer");
freshKraiken.setStakingPool(makeAddr("somePool"));
}
function testOnlyLiquidityManagerModifier() public {
address nonLM = makeAddr("notLiquidityManager");
vm.prank(nonLM);
vm.expectRevert("only liquidity manager");
kraiken.mint(1000 * 1e18);
}
// ========================================
// ZERO AMOUNT TESTS
// ========================================
function testMintZeroAmount() public {
// Mint a positive amount first so previousTotalSupply gets set
vm.prank(address(liquidityManager));
kraiken.mint(1000 * 1e18);
uint256 totalSupplyBefore = kraiken.totalSupply();
assertGt(kraiken.previousTotalSupply(), 0, "previousTotalSupply should be non-zero after first mint");
// Mint zero: should skip the entire amount > 0 block
// AND should skip the previousTotalSupply == 0 block (it is already set)
vm.prank(address(liquidityManager));
kraiken.mint(0);
assertEq(kraiken.totalSupply(), totalSupplyBefore, "Total supply must not change when minting zero");
assertEq(kraiken.balanceOf(stakingPool), 0, "Staking pool balance must not change when minting zero");
}
function testBurnZeroAmount() public {
vm.prank(address(liquidityManager));
kraiken.mint(1000 * 1e18);
uint256 totalSupplyBefore = kraiken.totalSupply();
// Burn zero: should skip the entire amount > 0 block
vm.prank(address(liquidityManager));
kraiken.burn(0);
assertEq(kraiken.totalSupply(), totalSupplyBefore, "Total supply must not change when burning zero");
}
}

View file

@ -445,4 +445,81 @@ contract VWAPTrackerTest is Test {
// Verify mathematical consistency
assertEq(minPriceForOverflow, minProductForOverflow / maxReasonableFee, "Price calculation correct");
}
// ========================================
// SINGLE-TRANSACTION OVERFLOW PROTECTION
// ========================================
/**
* @notice Test the ultra-rare single-transaction overflow protection (lines 36-41 of VWAPTracker)
* @dev Uses a price so large that price * volume exceeds type(uint256).max / 2 without
* itself overflowing uint256. This exercises the cap-and-recalculate branch.
*/
function testSingleTransactionOverflowProtection() public {
// Choose price such that price * (fee * 100) > type(uint256).max / 2
// but the multiplication itself does NOT overflow uint256.
//
// price = type(uint256).max / 200, fee = 2
// volume = fee * 100 = 200
// volumeWeightedPrice = (type(uint256).max / 200) * 200
// = type(uint256).max - (type(uint256).max % 200) safely below max
// >> type(uint256).max / 2 triggers the guard
uint256 extremePrice = type(uint256).max / 200;
uint256 largeFee = 2;
vwapTracker.recordVolumeAndPrice(extremePrice, largeFee);
// After the cap: volumeWeightedPrice = type(uint256).max / 2
// volume = (type(uint256).max / 2) / extremePrice
uint256 cappedVWP = type(uint256).max / 2;
uint256 expectedVolume = cappedVWP / extremePrice;
assertEq(
vwapTracker.cumulativeVolumeWeightedPriceX96(), cappedVWP, "Single-tx overflow: cumulative VWAP should be capped"
);
assertEq(vwapTracker.cumulativeVolume(), expectedVolume, "Single-tx overflow: volume should be recalculated from cap");
// VWAP should equal the extreme price (capped numerator / recalculated denominator)
assertEq(vwapTracker.getVWAP(), extremePrice, "VWAP should equal the extreme price after cap");
}
// ========================================
// MAXIMUM COMPRESSION FACTOR (>1000x) TEST
// ========================================
/**
* @notice Test that compressionFactor is capped at 1000 when historical data is very large
* @dev Sets cumulativeVolumeWeightedPriceX96 to type(uint256).max / 100 so that
* compressionFactor = (max/100) / (max/10^6) + 1 = 10000 + 1 > 1000 capped to 1000.
*/
function testMaxCompressionFactorCapped() public {
// maxSafeValue = type(uint256).max / 10^6
// compressionFactor = largeVWAP / maxSafeValue + 1 = (max/100)/(max/10^6) + 1 = 10^4 + 1
// Since 10^4 + 1 > 1000, it must be capped to 1000.
uint256 largeVWAP = type(uint256).max / 100;
uint256 largeVolume = 10 ** 20;
vm.store(address(vwapTracker), bytes32(uint256(0)), bytes32(largeVWAP));
vm.store(address(vwapTracker), bytes32(uint256(1)), bytes32(largeVolume));
vwapTracker.recordVolumeAndPrice(SAMPLE_PRICE_X96, SAMPLE_FEE);
uint256 compressionFactor = 1000; // capped from 10001
uint256 newVolume = SAMPLE_FEE * 100;
uint256 newVWP = SAMPLE_PRICE_X96 * newVolume;
uint256 expectedCumulativeVWAP = largeVWAP / compressionFactor + newVWP;
uint256 expectedCumulativeVolume = largeVolume / compressionFactor + newVolume;
assertEq(
vwapTracker.cumulativeVolumeWeightedPriceX96(),
expectedCumulativeVWAP,
"Max compression: cumulative VWAP should be compressed by exactly 1000"
);
assertEq(
vwapTracker.cumulativeVolume(),
expectedCumulativeVolume,
"Max compression: cumulative volume should be compressed by exactly 1000"
);
}
}