- Enhanced LiquidityManager test infrastructure with position contiguity checking - Added tick range validation to Response struct and checkLiquidity() function - Implemented proper assertions for testRecordVolumeAndPriceUnsafe() fuzzing test - Added anchor-discovery contiguity validation for both token orderings - Improved VWAP overflow detection testing with contract state validation - Updated testing todos with completion status and priority analysis 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
155 lines
No EOL
7.4 KiB
Solidity
155 lines
No EOL
7.4 KiB
Solidity
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "forge-std/Test.sol";
|
|
import "../src/VWAPTracker.sol";
|
|
import "./mocks/MockVWAPTracker.sol";
|
|
|
|
/**
|
|
* @title VWAP Double-Overflow Analysis
|
|
* @notice Analyzes the realistic possibility of double-overflow scenarios
|
|
* @dev Tests whether the minimal compression approach could lead to situations
|
|
* where new volume cannot be recorded due to overflow even after compression
|
|
*/
|
|
|
|
contract ExtendedMockVWAPTracker is MockVWAPTracker {
|
|
// No changes needed - keep the simple mock
|
|
}
|
|
|
|
contract VWAPDoubleOverflowAnalysisTest is Test {
|
|
ExtendedMockVWAPTracker vwapTracker;
|
|
|
|
function setUp() public {
|
|
vwapTracker = new ExtendedMockVWAPTracker();
|
|
}
|
|
|
|
/**
|
|
* @notice Fuzzing test with proper assertions for recording behavior and overflow detection
|
|
* @param currentPriceX96 Random price value (will be bounded)
|
|
* @param fee Random fee value (will be bounded)
|
|
*/
|
|
function testRecordVolumeAndPriceUnsafe(uint256 currentPriceX96, uint256 fee) public {
|
|
// Cap extreme inputs to prevent overflow during test calculations
|
|
if (currentPriceX96 > type(uint128).max) currentPriceX96 = type(uint128).max;
|
|
if (fee > type(uint64).max) fee = type(uint64).max;
|
|
if (currentPriceX96 == 0) currentPriceX96 = 1;
|
|
if (fee == 0) fee = 1;
|
|
|
|
// Store initial state BY READING FROM CONTRACT
|
|
uint256 initialCumulative = vwapTracker.cumulativeVolumeWeightedPriceX96();
|
|
uint256 initialVolume = vwapTracker.cumulativeVolume();
|
|
|
|
// Calculate expected values
|
|
uint256 volume = fee * 100;
|
|
uint256 volumeWeightedPriceX96 = currentPriceX96 * volume;
|
|
|
|
// Check if multiplication would overflow (extreme single transaction case)
|
|
if (currentPriceX96 > type(uint256).max / volume) {
|
|
// This is an extreme edge case - test should handle gracefully
|
|
return;
|
|
}
|
|
|
|
// INTERACT WITH CONTRACT: Actually record the data
|
|
vwapTracker.recordVolumeAndPrice(currentPriceX96, fee);
|
|
|
|
// ASSERT: Verify recording behavior BY READING FROM CONTRACT
|
|
uint256 newCumulative = vwapTracker.cumulativeVolumeWeightedPriceX96();
|
|
uint256 newVolume = vwapTracker.cumulativeVolume();
|
|
|
|
if (volumeWeightedPriceX96 > type(uint256).max / 2) {
|
|
// Should cap extreme single transactions
|
|
assertTrue(newCumulative > initialCumulative, "Should have recorded something");
|
|
assertTrue(newCumulative < initialCumulative + type(uint256).max / 2, "Should have capped extreme transaction");
|
|
} else if (initialCumulative > 10**70) {
|
|
// Should trigger compression
|
|
assertTrue(newCumulative < initialCumulative, "Should have compressed on overflow");
|
|
assertTrue(newVolume < initialVolume, "Volume should also be compressed");
|
|
// But should still record the new data
|
|
assertTrue(newCumulative > 0, "Should have recorded new data after compression");
|
|
assertTrue(newVolume > 0, "Should have recorded new volume after compression");
|
|
} else {
|
|
// Should record normally
|
|
assertEq(newCumulative, initialCumulative + volumeWeightedPriceX96, "Should record exact values");
|
|
assertEq(newVolume, initialVolume + volume, "Should record exact volume");
|
|
}
|
|
|
|
// ASSERT: VWAP calculation should always work BY READING FROM CONTRACT
|
|
uint256 vwap = vwapTracker.getVWAP();
|
|
if (newVolume > 0) {
|
|
assertGt(vwap, 0, "VWAP should be non-zero when volume exists");
|
|
assertEq(vwap, newCumulative / newVolume, "VWAP should match manual calculation");
|
|
} else {
|
|
assertEq(vwap, 0, "VWAP should be zero when no volume exists");
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @notice Tests the actual compression behavior under extreme but realistic conditions
|
|
*/
|
|
function testCompressionUnderExtremeConditions() public {
|
|
console.log("\n=== COMPRESSION BEHAVIOR TEST ===");
|
|
|
|
// Simulate a scenario with very large accumulated data
|
|
uint256 largeValue = 10**70 + 1; // Triggers compression
|
|
vm.store(address(vwapTracker), bytes32(uint256(0)), bytes32(largeValue));
|
|
vm.store(address(vwapTracker), bytes32(uint256(1)), bytes32(largeValue / 10**30));
|
|
|
|
console.log("Before compression trigger:");
|
|
console.log("Cumulative VWAP:", vwapTracker.cumulativeVolumeWeightedPriceX96());
|
|
console.log("Cumulative Volume:", vwapTracker.cumulativeVolume());
|
|
|
|
// Try to record a large but realistic transaction
|
|
uint256 realisticHighPrice = (uint256(1000) << 96) / 3000; // $1000 HARB / $3000 ETH
|
|
uint256 largeFee = 100 ether; // 100 ETH trade
|
|
|
|
console.log("Recording large transaction:");
|
|
console.log("Price X96:", realisticHighPrice);
|
|
console.log("Fee:", largeFee / 10**18, "ETH");
|
|
|
|
// This should trigger compression and succeed
|
|
vwapTracker.recordVolumeAndPrice(realisticHighPrice, largeFee);
|
|
|
|
console.log("After recording (post-compression):");
|
|
console.log("Cumulative VWAP:", vwapTracker.cumulativeVolumeWeightedPriceX96());
|
|
console.log("Cumulative Volume:", vwapTracker.cumulativeVolume());
|
|
console.log("Final VWAP:", vwapTracker.getVWAP());
|
|
|
|
// Verify it worked without reverting
|
|
assertTrue(vwapTracker.cumulativeVolumeWeightedPriceX96() > 0, "Transaction should have been recorded successfully");
|
|
console.log("SUCCESS: Large transaction recorded even under extreme conditions");
|
|
}
|
|
|
|
/**
|
|
* @notice Tests if we can create a scenario that actually fails due to double-overflow
|
|
*/
|
|
function testAttemptToCreateDoubleOverflow() public {
|
|
console.log("\n=== ATTEMPT TO CREATE DOUBLE-OVERFLOW ===");
|
|
|
|
// Set up state that's already maximally compressed (1000x was applied)
|
|
uint256 maxSafeAfterCompression = type(uint256).max / 10**6;
|
|
vm.store(address(vwapTracker), bytes32(uint256(0)), bytes32(maxSafeAfterCompression));
|
|
vm.store(address(vwapTracker), bytes32(uint256(1)), bytes32(maxSafeAfterCompression / 10**40));
|
|
|
|
// Try an impossibly large transaction that might cause double-overflow
|
|
uint256 impossiblePrice = type(uint128).max; // Maximum reasonable price
|
|
uint256 impossibleFee = type(uint64).max; // Maximum reasonable fee
|
|
|
|
console.log("Attempting impossible transaction:");
|
|
console.log("Price:", impossiblePrice);
|
|
console.log("Fee:", impossibleFee);
|
|
console.log("Product:", impossiblePrice * impossibleFee * 100);
|
|
|
|
// This should either succeed (with compression) or provide insight into edge case
|
|
try vwapTracker.recordVolumeAndPrice(impossiblePrice, impossibleFee) {
|
|
console.log("SUCCESS: Even impossible transaction was handled");
|
|
console.log("Final VWAP after impossible transaction:", vwapTracker.getVWAP());
|
|
} catch Error(string memory reason) {
|
|
console.log("FAILED: Found the double-overflow edge case");
|
|
console.log("Error reason:", reason);
|
|
// If this fails, we've found a legitimate edge case that needs addressing
|
|
} catch {
|
|
console.log("FAILED: Low-level failure in double-overflow scenario");
|
|
}
|
|
}
|
|
} |