From db1c26838de4fca698efd4b478f15db3e7e3f7cc Mon Sep 17 00:00:00 2001 From: johba Date: Sun, 22 Mar 2026 20:31:04 +0000 Subject: [PATCH] fix: _isPriceStable fallback interval can still revert on pools with very short history (#610) Wrap the fallback pool.observe() call in a try/catch so that pools with insufficient observation history for both the primary (30s) and fallback (6000s) intervals return false (price unstable) instead of reverting with an opaque Uniswap V3 error. This prevents recenter() from failing for unpermissioned callers on newly created pools. Co-Authored-By: Claude Opus 4.6 (1M context) --- onchain/src/abstracts/PriceOracle.sol | 12 +++++++++--- onchain/test/abstracts/PriceOracle.t.sol | 9 +++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/onchain/src/abstracts/PriceOracle.sol b/onchain/src/abstracts/PriceOracle.sol index 3a91b13..2645d29 100644 --- a/onchain/src/abstracts/PriceOracle.sol +++ b/onchain/src/abstracts/PriceOracle.sol @@ -43,9 +43,15 @@ abstract contract PriceOracle { // Fallback to longer timeframe if recent data unavailable uint32 fallbackInterval = PRICE_STABILITY_INTERVAL * 200; // 6,000 seconds secondsAgo[0] = fallbackInterval; - (int56[] memory tickCumulatives,) = pool.observe(secondsAgo); - int56 tickCumulativeDiff = tickCumulatives[1] - tickCumulatives[0]; - averageTick = int24(tickCumulativeDiff / int56(int32(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); diff --git a/onchain/test/abstracts/PriceOracle.t.sol b/onchain/test/abstracts/PriceOracle.t.sol index 5882127..a0d9460 100644 --- a/onchain/test/abstracts/PriceOracle.t.sol +++ b/onchain/test/abstracts/PriceOracle.t.sol @@ -228,6 +228,15 @@ contract PriceOracleTest is Test { assertTrue(isStable, "Price stability should work with negative ticks"); } + function testDoubleFailureReturnsFalse() public { + // When pool has < 6000 seconds of history, both observe() calls fail. + // _isPriceStable should return false (safe default) instead of reverting. + mockPool.setShouldRevert(true); + + bool isStable = priceOracle.isPriceStable(1000); + assertFalse(isStable, "Double observe failure should return false, not revert"); + } + // ======================================== // PRICE MOVEMENT VALIDATION TESTS // ========================================