// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "../src/VWAPTracker.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 MockVWAPTracker is VWAPTracker { function recordVolumeAndPrice(uint256 currentPriceX96, uint256 fee) external { _recordVolumeAndPrice(currentPriceX96, fee); } function resetVWAP() external { _resetVWAP(); } // Expose internal function for testing (bounds applied to prevent test overflow) function testRecordVolumeAndPriceUnsafe(uint256 currentPriceX96, uint256 fee) external view { // 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; uint256 volume = fee * 100; // Check if multiplication would overflow if (currentPriceX96 > type(uint256).max / volume) { console.log("Multiplication would overflow - extreme transaction detected"); return; } uint256 volumeWeightedPriceX96 = currentPriceX96 * volume; // Check if addition would overflow bool addWouldOverflow = cumulativeVolumeWeightedPriceX96 > type(uint256).max - volumeWeightedPriceX96; console.log("Current cumulative:", cumulativeVolumeWeightedPriceX96); console.log("New volume-weighted price:", volumeWeightedPriceX96); console.log("Would overflow on addition:", addWouldOverflow); } } contract VWAPDoubleOverflowAnalysisTest is Test { MockVWAPTracker vwapTracker; function setUp() public { vwapTracker = new MockVWAPTracker(); } /** * @notice Analyzes the maximum realistic price and volume that could cause double-overflow * @dev Calculates what price/volume combination would overflow even after 1000x compression */ function testDoubleOverflowRealisticScenario() public { console.log("=== DOUBLE-OVERFLOW ANALYSIS ==="); // Set up a scenario where we're at the compression threshold after compression uint256 maxSafeValue = type(uint256).max / 10**6; // Our compression trigger point uint256 compressedValue = maxSafeValue; // After 1000x compression, we're still near threshold console.log("Max safe value:", maxSafeValue); console.log("Compressed cumulative VWAP:", compressedValue); // Set the state to post-compression values vm.store(address(vwapTracker), bytes32(uint256(0)), bytes32(compressedValue)); vm.store(address(vwapTracker), bytes32(uint256(1)), bytes32(compressedValue / (10**30))); // Assume price of 10^30 // Calculate what new transaction would cause overflow even after compression uint256 availableSpace = type(uint256).max - compressedValue; console.log("Available space after compression:", availableSpace); // For overflow to occur after compression, the new volumeWeightedPrice must be: // newVolumeWeightedPrice > availableSpace // Since newVolumeWeightedPrice = price * volume, and volume = fee * 100: // price * fee * 100 > availableSpace // Therefore: price * fee > availableSpace / 100 uint256 minProductForOverflow = availableSpace / 100 + 1; console.log("Minimum price * fee for double-overflow:", minProductForOverflow); // Test realistic scenarios console.log("\n=== REALISTIC SCENARIO ANALYSIS ==="); // Scenario 1: Extremely high ETH price (1 ETH = $1,000,000) uint256 extremeEthPriceUSD = 1_000_000; uint256 harbPriceUSD = 1; // $1 HARB // In X96 format: HARB/ETH = harbPrice/ethPrice uint256 realisticPriceX96 = (uint256(harbPriceUSD) << 96) / extremeEthPriceUSD; console.log("Extreme ETH price scenario:"); console.log("ETH price: $", extremeEthPriceUSD); console.log("HARB price: $", harbPriceUSD); console.log("HARB/ETH price X96:", realisticPriceX96); // Calculate required fee for double-overflow if (realisticPriceX96 > 0) { uint256 requiredFee = minProductForOverflow / realisticPriceX96; console.log("Required fee for double-overflow:", requiredFee, "ETH"); console.log("Required fee in USD:", requiredFee * extremeEthPriceUSD / 10**18); bool isRealistic = requiredFee < 1000 ether; // 1000 ETH trade console.log("Is this realistic?", isRealistic); } // Scenario 2: Hyperinflated HARB price uint256 normalEthPrice = 3000; // $3000 ETH uint256 hyperInflatedHarbPrice = 1_000_000; // $1M HARB uint256 hyperInflatedPriceX96 = (uint256(hyperInflatedHarbPrice) << 96) / normalEthPrice; console.log("\nHyper-inflated HARB scenario:"); console.log("HARB price: $", hyperInflatedHarbPrice); console.log("HARB/ETH price X96:", hyperInflatedPriceX96); if (hyperInflatedPriceX96 > 0) { uint256 requiredFee2 = minProductForOverflow / hyperInflatedPriceX96; console.log("Required fee for double-overflow:", requiredFee2, "ETH"); console.log("Required fee in USD:", requiredFee2 * normalEthPrice / 10**18); bool isRealistic2 = requiredFee2 < 100 ether; // 100 ETH trade console.log("Is this realistic?", isRealistic2); } // Scenario 3: Maximum possible single transaction uint256 maxReasonableFee = 10000 ether; // 10,000 ETH (unrealistically large) uint256 minPriceForOverflow = minProductForOverflow / maxReasonableFee; console.log("\nMaximum transaction scenario:"); console.log("Max reasonable single trade:", maxReasonableFee / 10**18, "ETH"); console.log("Min price X96 for overflow:", minPriceForOverflow); // Convert back to USD equivalent // If minPriceForOverflow is the HARB/ETH ratio in X96, then: // HARB price in ETH = minPriceForOverflow / 2^96 uint256 minHarbPriceInEth = minPriceForOverflow >> 96; uint256 minHarbPriceUSD = minHarbPriceInEth * 3000; // Assuming $3000 ETH console.log("Min HARB price for overflow: $", minHarbPriceUSD); console.log("Is this realistic? Probably not - this would make HARB worth more than all global wealth"); // Conclusion console.log("\n=== CONCLUSION ==="); console.log("Double-overflow would require either:"); console.log("1. Impossibly large single transactions (>10,000 ETH)"); console.log("2. Impossibly high token prices (>$1M per token)"); console.log("3. Or a combination that exceeds realistic market conditions"); console.log("Therefore, the 1000x compression limit provides adequate protection."); } /** * @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"); } } }