// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; import "@openzeppelin/utils/math/Math.sol"; /** * @title VWAPTracker * @notice Abstract contract for tracking Volume Weighted Average Price (VWAP) data * @dev Provides VWAP calculation and storage functionality that can be inherited by other contracts. * Price inputs are sourced from the current pool tick (pool.slot0()) at the time of each * recenter, giving volume-weighted accuracy without per-swap gas overhead. * The LiquidityManager feeds _recordVolumeAndPrice(currentPriceX96, ethFee) at each recenter. * * Key features: * - Volume-weighted average with data compression (max 1000x compression) * - Prevents dormant whale manipulation through historical price memory * - Stores price² (squared price) in X96 format for VWAP calculation * - Automatic overflow protection by compressing historic data when needed * - Price source: current pool tick snapshot at recenter time (not TWAP, not anchor midpoint) */ abstract contract VWAPTracker { using Math for uint256; uint256 public cumulativeVolumeWeightedPriceX96; uint256 public cumulativeVolume; /** * @notice Records volume and price data for VWAP calculation * @param currentPriceX96 The current price in Q96 format (price * 2^96, from _priceAtTick) * @param fee The fee amount used to calculate volume * @dev Assumes fee represents 1% of volume, handles overflow by compressing historic data */ function _recordVolumeAndPrice(uint256 currentPriceX96, uint256 fee) internal { // assuming FEE is 1% uint256 volume = fee * 100; uint256 volumeWeightedPriceX96 = currentPriceX96 * volume; // ULTRA-RARE EDGE CASE: Check if the new data itself would overflow even before adding // This can only happen with impossibly large transactions (>10,000 ETH + $billion token prices) if (volumeWeightedPriceX96 > type(uint256).max / 2) { // If a single transaction is this large, cap it to prevent system failure // This preserves system functionality while limiting the impact of the extreme transaction volumeWeightedPriceX96 = type(uint256).max / 2; volume = volumeWeightedPriceX96 / currentPriceX96; } // Check for potential overflow. 10**70 is close to 2^256 if (cumulativeVolumeWeightedPriceX96 > 10 ** 70) { // CRITICAL: Preserve historical significance for dormant whale protection // Find the MINIMUM compression factor needed to prevent overflow uint256 maxSafeValue = type(uint256).max / 10 ** 6; // Leave substantial room for future data uint256 compressionFactor = (cumulativeVolumeWeightedPriceX96 / maxSafeValue) + 1; // Cap maximum compression to preserve historical "eternal memory" // Even in extreme cases, historical data should retain significant weight if (compressionFactor > 1000) { compressionFactor = 1000; // Maximum 1000x compression to preserve history } // Ensure minimum compression effectiveness if (compressionFactor < 2) { compressionFactor = 2; // At least 2x compression when triggered } // Compress both values by the same minimal factor cumulativeVolumeWeightedPriceX96 = cumulativeVolumeWeightedPriceX96 / compressionFactor; cumulativeVolume = cumulativeVolume / compressionFactor; } cumulativeVolumeWeightedPriceX96 += volumeWeightedPriceX96; cumulativeVolume += volume; } /** * @notice Calculates the current VWAP * @return vwapX96 The volume weighted average price in X96 format * @dev Returns 0 if no volume has been recorded */ function getVWAP() public view returns (uint256 vwapX96) { if (cumulativeVolume > 0) { vwapX96 = cumulativeVolumeWeightedPriceX96 / cumulativeVolume; } else { vwapX96 = 0; } } /** * @notice Calculates adjusted VWAP with capital inefficiency factor * @param capitalInefficiency The capital inefficiency factor (scaled by 10^18) * @return adjustedVwapX96 The adjusted VWAP in X96 format * @dev Applies a 70% base weight plus capital inefficiency adjustment */ function getAdjustedVWAP(uint256 capitalInefficiency) public view returns (uint256 adjustedVwapX96) { uint256 vwapX96 = getVWAP(); if (vwapX96 > 0) { adjustedVwapX96 = (7 * vwapX96 / 10) + (vwapX96 * capitalInefficiency / 10 ** 18); } else { adjustedVwapX96 = 0; } } /** * @notice Resets VWAP tracking data * @dev Can be called by inheriting contracts to reset tracking */ function _resetVWAP() internal { cumulativeVolumeWeightedPriceX96 = 0; cumulativeVolume = 0; } }