## Changes ### Configuration - Added .solhint.json with recommended rules + custom config - 160 char line length (warn) - Double quotes enforcement (error) - Explicit visibility required (error) - Console statements allowed (scripts/tests need them) - Gas optimization warnings enabled - Ignores test/helpers/, lib/, out/, cache/, broadcast/ - Added foundry.toml [fmt] section - 160 char line length - 4-space tabs - Double quotes - Thousands separators for numbers - Sort imports enabled - Added .lintstagedrc.json for pre-commit auto-fix - Runs solhint --fix on .sol files - Runs forge fmt on .sol files - Added husky pre-commit hook via lint-staged ### NPM Scripts - lint:sol - run solhint - lint:sol:fix - auto-fix solhint issues - format:sol - format with forge fmt - format:sol:check - check formatting - lint / lint:fix - combined commands ### Code Changes - Added explicit visibility modifiers (internal) to constants in scripts and tests - Fixed quote style in DeployLocal.sol - All Solidity files formatted with forge fmt ## Verification - ✅ forge fmt --check passes - ✅ No solhint errors (warnings only) - ✅ forge build succeeds - ✅ forge test passes (107/107) resolves #44 Co-authored-by: johba <johba@harb.eth> Reviewed-on: https://codeberg.org/johba/harb/pulls/51
69 lines
3.1 KiB
Solidity
69 lines
3.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 (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;
|
|
}
|
|
}
|