- Fix misleading taxRate comment in AttackRunner.s.sol (index into TAX_RATES[], not raw rate) - Clarify _validatePriceMovement NatSpec return doc in PriceOracle.sol - Remove redundant double-cast uint256(uint256(...)) in OptimizerV3Push3Lib.sol - Add Basescan URL source comments for SWAP_ROUTER and WETH addresses Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
78 lines
4.1 KiB
Solidity
78 lines
4.1 KiB
Solidity
// 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 (30 seconds)
|
|
/// @dev Reduced from 300s (5 min) to 30s to align with MIN_RECENTER_INTERVAL (60s).
|
|
/// The old 300s window caused recenter to revert with "price deviated from oracle"
|
|
/// for ~285s after any significant trade (> ~1000-tick move), creating a window
|
|
/// where the LiquidityManager could not reposition and an adversary with parasitic
|
|
/// LP could extract value from passive holders (issue #517).
|
|
/// 30s still prevents same-block manipulation on Ethereum mainnet (~12s block time)
|
|
/// while ensuring TWAP converges well within the 60s cooldown period.
|
|
uint32 internal constant PRICE_STABILITY_INTERVAL = 30;
|
|
/// @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; // 30 seconds 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
|
|
uint32 fallbackInterval = PRICE_STABILITY_INTERVAL * 200; // 6,000 seconds
|
|
secondsAgo[0] = fallbackInterval;
|
|
try pool.observe(secondsAgo) returns (int56[] memory fallbackCumulatives, uint160[] memory) {
|
|
int56 tickCumulativeDiff = fallbackCumulatives[1] - fallbackCumulatives[0];
|
|
averageTick = int24(tickCumulativeDiff / int56(int32(fallbackInterval)));
|
|
} catch {
|
|
// Pool has insufficient observation history for both intervals.
|
|
// Treat price as unstable (safe default) — prevents recenter() from
|
|
// reverting with an opaque Uniswap error on pools with very short history.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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 ETH price moved up (WETH appreciating vs KRK); accounts for token0isWeth 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;
|
|
}
|
|
}
|