harb/onchain/src/VWAPTracker.sol
openhands 85350caf52 feat: OptimizerV3 with direct 2D staking-to-LP parameter mapping
Core protocol changes for launch readiness:

- OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids
  exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18.
  Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48].
- Directional VWAP: only records prices on ETH inflow (buys), preventing
  sell-side dilution of price memory
- Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses
  distance from adjusted VWAP as floor distance, no branching
- PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s)
- Access control (M-2 fix): deployer-only guard on one-time setters
- Recenter rate limit (M-3 fix): 60-second cooldown for open recenters
- Safe fallback params: recenter() optimizer-failure defaults changed from
  exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100
- Recentered event for monitoring and indexing
- VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:21:18 +00:00

104 lines
4.5 KiB
Solidity

// 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
*
* 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
*/
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;
}
}