// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; /// @title Oracle /// @notice Provides price and liquidity data useful for a wide variety of system designs /// @dev Instances of stored oracle data, "observations", are collected in the oracle array /// Every pool is initialized with an oracle array length of 1. Anyone can pay the SSTOREs to increase the /// maximum length of the oracle array. New slots will be added when the array is fully populated. /// Observations are overwritten when the full length of the oracle array is populated. /// The most recent observation is available, independent of the length of the oracle array, by passing 0 to observe() library V3Oracle { struct Observation { // the block timestamp of the observation uint32 blockTimestamp; // the tick accumulator, i.e. tick * time elapsed since the pool was first initialized int56 tickCumulative; // whether or not the observation is initialized bool initialized; } /// @notice Transforms a previous observation into a new observation, given the passage of time and the current tick and liquidity values /// @dev blockTimestamp _must_ be chronologically equal to or greater than last.blockTimestamp, safe for 0 or 1 overflows /// @param last The specified observation to be transformed /// @param blockTimestamp The timestamp of the new observation /// @param tick The active tick at the time of the new observation /// @return Observation The newly populated observation function transform( Observation memory last, uint32 blockTimestamp, int24 tick ) private pure returns (Observation memory) { uint32 delta = blockTimestamp - last.blockTimestamp; return Observation({ blockTimestamp: blockTimestamp, tickCumulative: last.tickCumulative + int56(tick) * int32(delta), initialized: true }); } function initialize(Observation[65535] storage self, uint32 time) internal returns (uint16 cardinality, uint16 cardinalityNext) { self[0] = Observation({ blockTimestamp: time, tickCumulative: 0, initialized: true }); return (1, 1); } function write( Observation[65535] storage self, uint16 index, uint32 blockTimestamp, int24 tick, uint16 cardinality, uint16 cardinalityNext ) internal returns (uint16 indexUpdated, uint16 cardinalityUpdated) { Observation memory last = self[index]; // early return if we've already written an observation this block if (last.blockTimestamp == blockTimestamp) return (index, cardinality); // if the conditions are right, we can bump the cardinality if (cardinalityNext > cardinality && index == (cardinality - 1)) { cardinalityUpdated = cardinalityNext; } else { cardinalityUpdated = cardinality; } indexUpdated = (index + 1) % cardinalityUpdated; self[indexUpdated] = transform(last, blockTimestamp, tick); } function grow( Observation[65535] storage self, uint16 current, uint16 next ) internal returns (uint16) { require(current > 0, 'I'); // no-op if the passed next value isn't greater than the current next value if (next <= current) return current; // store in each slot to prevent fresh SSTOREs in swaps // this data will not be used because the initialized boolean is still false for (uint16 i = current; i < next; i++) self[i].blockTimestamp = 1; return next; } function lte( uint32 time, uint32 a, uint32 b ) private pure returns (bool) { // if there hasn't been overflow, no need to adjust if (a <= time && b <= time) return a <= b; uint256 aAdjusted = a > time ? a : a + 2**32; uint256 bAdjusted = b > time ? b : b + 2**32; return aAdjusted <= bAdjusted; } function binarySearch( Observation[65535] storage self, uint32 time, uint32 target, uint16 index, uint16 cardinality ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) { uint256 l = (index + 1) % cardinality; // oldest observation uint256 r = l + cardinality - 1; // newest observation uint256 i; while (true) { i = (l + r) / 2; beforeOrAt = self[i % cardinality]; // we've landed on an uninitialized tick, keep searching higher (more recently) if (!beforeOrAt.initialized) { l = i + 1; continue; } atOrAfter = self[(i + 1) % cardinality]; bool targetAtOrAfter = lte(time, beforeOrAt.blockTimestamp, target); // check if we've found the answer! if (targetAtOrAfter && lte(time, target, atOrAfter.blockTimestamp)) break; if (!targetAtOrAfter) r = i - 1; else l = i + 1; } } function getSurroundingObservations( Observation[65535] storage self, uint32 time, uint32 target, int24 tick, uint16 index, uint16 cardinality ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) { // optimistically set before to the newest observation beforeOrAt = self[index]; // if the target is chronologically at or after the newest observation, we can early return if (lte(time, beforeOrAt.blockTimestamp, target)) { if (beforeOrAt.blockTimestamp == target) { // if newest observation equals target, we're in the same block, so we can ignore atOrAfter return (beforeOrAt, atOrAfter); } else { // otherwise, we need to transform return (beforeOrAt, transform(beforeOrAt, target, tick)); } } // now, set before to the oldest observation beforeOrAt = self[(index + 1) % cardinality]; if (!beforeOrAt.initialized) beforeOrAt = self[0]; // ensure that the target is chronologically at or after the oldest observation require(lte(time, beforeOrAt.blockTimestamp, target), 'OLD'); // if we've reached this point, we have to binary search return binarySearch(self, time, target, index, cardinality); } function observeSingle( Observation[65535] storage self, uint32 time, uint32 secondsAgo, int24 tick, uint16 index, uint16 cardinality ) internal view returns (int56 tickCumulative) { if (secondsAgo == 0) { Observation memory last = self[index]; if (last.blockTimestamp != time) last = transform(last, time, tick); return last.tickCumulative; } uint32 target = time - secondsAgo; (Observation memory beforeOrAt, Observation memory atOrAfter) = getSurroundingObservations(self, time, target, tick, index, cardinality); if (target == beforeOrAt.blockTimestamp) { // we're at the left boundary return beforeOrAt.tickCumulative; } else if (target == atOrAfter.blockTimestamp) { // we're at the right boundary return atOrAfter.tickCumulative; } else { // we're in the middle uint32 observationTimeDelta = atOrAfter.blockTimestamp - beforeOrAt.blockTimestamp; uint32 targetDelta = target - beforeOrAt.blockTimestamp; return beforeOrAt.tickCumulative + ((atOrAfter.tickCumulative - beforeOrAt.tickCumulative) / int32(observationTimeDelta)) * int32(targetDelta); } } function observe( Observation[65535] storage self, uint32 time, uint32[] memory secondsAgos, int24 tick, uint16 index, uint16 cardinality ) internal view returns (int56[] memory tickCumulatives) { require(cardinality > 0, 'I'); tickCumulatives = new int56[](secondsAgos.length); for (uint256 i = 0; i < secondsAgos.length; i++) { tickCumulatives[i] = observeSingle( self, time, secondsAgos[i], tick, index, cardinality ); } } }