// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; import "@openzeppelin/utils/math/SignedMath.sol"; import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol"; /** * @title PriceOracle * @notice Abstract contract providing price stability validation using Uniswap V3 TWAP oracle * @dev Contains oracle-related functionality for validating price movements and stability */ abstract contract PriceOracle { /// @notice Interval for price stability checks (5 minutes) uint32 internal constant PRICE_STABILITY_INTERVAL = 300; /// @notice Maximum allowed tick deviation from TWAP average int24 internal constant MAX_TICK_DEVIATION = 50; /// @notice The Uniswap V3 pool used for oracle data function _getPool() internal view virtual returns (IUniswapV3Pool); /// @notice Validates if the current price is stable compared to TWAP oracle /// @param currentTick The current tick to validate /// @return isStable True if price is within acceptable deviation from TWAP function _isPriceStable(int24 currentTick) internal view returns (bool isStable) { IUniswapV3Pool pool = _getPool(); uint32[] memory secondsAgo = new uint32[](2); secondsAgo[0] = PRICE_STABILITY_INTERVAL; // 5 minutes ago secondsAgo[1] = 0; // current block timestamp int24 averageTick; try pool.observe(secondsAgo) returns (int56[] memory tickCumulatives, uint160[] memory) { int56 tickCumulativeDiff = tickCumulatives[1] - tickCumulatives[0]; averageTick = int24(tickCumulativeDiff / int56(int32(PRICE_STABILITY_INTERVAL))); } catch { // Fallback to longer timeframe if recent data unavailable secondsAgo[0] = PRICE_STABILITY_INTERVAL * 200; (int56[] memory tickCumulatives,) = pool.observe(secondsAgo); int56 tickCumulativeDiff = tickCumulatives[1] - tickCumulatives[0]; averageTick = int24(tickCumulativeDiff / int56(int32(PRICE_STABILITY_INTERVAL))); } isStable = (currentTick >= averageTick - MAX_TICK_DEVIATION && currentTick <= averageTick + MAX_TICK_DEVIATION); } /// @notice Validates if price movement is sufficient for recentering /// @param currentTick The current market tick /// @param centerTick The center tick of the anchor position /// @param tickSpacing The tick spacing for minimum amplitude calculation /// @param token0isWeth Whether token0 is WETH (affects price direction logic) /// @return isUp True if price moved up (relative to token ordering) /// @return isEnough True if movement amplitude is sufficient for recentering function _validatePriceMovement( int24 currentTick, int24 centerTick, int24 tickSpacing, bool token0isWeth ) internal pure returns (bool isUp, bool isEnough) { uint256 minAmplitude = uint24(tickSpacing) * 2; // Determine the correct comparison direction based on token0isWeth isUp = token0isWeth ? currentTick < centerTick : currentTick > centerTick; isEnough = SignedMath.abs(currentTick - centerTick) > minAmplitude; } }