feat(oracle): add observation ring buffer for twap
This commit is contained in:
1 parent
040cec1c55
commit
6cc20bd1e4
1 file changed
+208
@@ -0,0 +1,208 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.36;
|
||||
|
||||
/// @title Ring buffer of cumulative-tick observations backing the TWAP oracle.
|
||||
library Oracle {
|
||||
struct Observation {
|
||||
uint32 blockTimestamp;
|
||||
int56 tickCumulative;
|
||||
uint160 secondsPerLiquidityCumulativeX128;
|
||||
bool initialized;
|
||||
}
|
||||
|
||||
function transform(Observation memory last, uint32 blockTimestamp, int24 tick, uint128 liquidity)
|
||||
private
|
||||
pure
|
||||
returns (Observation memory)
|
||||
{
|
||||
unchecked {
|
||||
uint32 delta = blockTimestamp - last.blockTimestamp;
|
||||
return Observation({
|
||||
blockTimestamp: blockTimestamp,
|
||||
tickCumulative: last.tickCumulative + int56(tick) * int56(uint56(delta)),
|
||||
secondsPerLiquidityCumulativeX128: last.secondsPerLiquidityCumulativeX128
|
||||
+ ((uint160(delta) << 128) / (liquidity > 0 ? liquidity : 1)),
|
||||
initialized: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initialize(Observation[65535] storage self, uint32 time)
|
||||
internal
|
||||
returns (uint16 cardinality, uint16 cardinalityNext)
|
||||
{
|
||||
self[0] = Observation({
|
||||
blockTimestamp: time, tickCumulative: 0, secondsPerLiquidityCumulativeX128: 0, initialized: true
|
||||
});
|
||||
return (1, 1);
|
||||
}
|
||||
|
||||
function write(
|
||||
Observation[65535] storage self,
|
||||
uint16 index,
|
||||
uint32 blockTimestamp,
|
||||
int24 tick,
|
||||
uint128 liquidity,
|
||||
uint16 cardinality,
|
||||
uint16 cardinalityNext
|
||||
) internal returns (uint16 indexUpdated, uint16 cardinalityUpdated) {
|
||||
unchecked {
|
||||
Observation memory last = self[index];
|
||||
|
||||
if (last.blockTimestamp == blockTimestamp) return (index, cardinality);
|
||||
|
||||
if (cardinalityNext > cardinality && index == (cardinality - 1)) {
|
||||
cardinalityUpdated = cardinalityNext;
|
||||
} else {
|
||||
cardinalityUpdated = cardinality;
|
||||
}
|
||||
|
||||
indexUpdated = (index + 1) % cardinalityUpdated;
|
||||
self[indexUpdated] = transform(last, blockTimestamp, tick, liquidity);
|
||||
}
|
||||
}
|
||||
|
||||
function grow(Observation[65535] storage self, uint16 current, uint16 next) internal returns (uint16) {
|
||||
unchecked {
|
||||
require(current > 0, "Oracle: NOT_INIT");
|
||||
if (next <= current) return current;
|
||||
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) {
|
||||
unchecked {
|
||||
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)
|
||||
{
|
||||
unchecked {
|
||||
uint256 l = (index + 1) % cardinality;
|
||||
uint256 r = l + cardinality - 1;
|
||||
uint256 i;
|
||||
while (true) {
|
||||
i = (l + r) / 2;
|
||||
|
||||
beforeOrAt = self[i % cardinality];
|
||||
|
||||
if (!beforeOrAt.initialized) {
|
||||
l = i + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
atOrAfter = self[(i + 1) % cardinality];
|
||||
|
||||
bool targetAtOrAfter = lte(time, beforeOrAt.blockTimestamp, target);
|
||||
|
||||
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,
|
||||
uint128 liquidity,
|
||||
uint16 cardinality
|
||||
) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) {
|
||||
unchecked {
|
||||
beforeOrAt = self[index];
|
||||
|
||||
if (lte(time, beforeOrAt.blockTimestamp, target)) {
|
||||
if (beforeOrAt.blockTimestamp == target) {
|
||||
return (beforeOrAt, atOrAfter);
|
||||
} else {
|
||||
return (beforeOrAt, transform(beforeOrAt, target, tick, liquidity));
|
||||
}
|
||||
}
|
||||
|
||||
beforeOrAt = self[(index + 1) % cardinality];
|
||||
if (!beforeOrAt.initialized) beforeOrAt = self[0];
|
||||
|
||||
require(lte(time, beforeOrAt.blockTimestamp, target), "Oracle: OLD");
|
||||
|
||||
return binarySearch(self, time, target, index, cardinality);
|
||||
}
|
||||
}
|
||||
|
||||
function observeSingle(
|
||||
Observation[65535] storage self,
|
||||
uint32 time,
|
||||
uint32 secondsAgo,
|
||||
int24 tick,
|
||||
uint16 index,
|
||||
uint128 liquidity,
|
||||
uint16 cardinality
|
||||
) internal view returns (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) {
|
||||
unchecked {
|
||||
if (secondsAgo == 0) {
|
||||
Observation memory last = self[index];
|
||||
if (last.blockTimestamp != time) last = transform(last, time, tick, liquidity);
|
||||
return (last.tickCumulative, last.secondsPerLiquidityCumulativeX128);
|
||||
}
|
||||
|
||||
uint32 target = time - secondsAgo;
|
||||
|
||||
(Observation memory beforeOrAt, Observation memory atOrAfter) =
|
||||
getSurroundingObservations(self, time, target, tick, index, liquidity, cardinality);
|
||||
|
||||
if (target == beforeOrAt.blockTimestamp) {
|
||||
return (beforeOrAt.tickCumulative, beforeOrAt.secondsPerLiquidityCumulativeX128);
|
||||
} else if (target == atOrAfter.blockTimestamp) {
|
||||
return (atOrAfter.tickCumulative, atOrAfter.secondsPerLiquidityCumulativeX128);
|
||||
} else {
|
||||
uint32 observationTimeDelta = atOrAfter.blockTimestamp - beforeOrAt.blockTimestamp;
|
||||
uint32 targetDelta = target - beforeOrAt.blockTimestamp;
|
||||
return (
|
||||
beforeOrAt.tickCumulative
|
||||
+ ((atOrAfter.tickCumulative - beforeOrAt.tickCumulative) / int56(uint56(observationTimeDelta)))
|
||||
* int56(uint56(targetDelta)),
|
||||
beforeOrAt.secondsPerLiquidityCumulativeX128
|
||||
+ uint160(
|
||||
(uint256(
|
||||
atOrAfter.secondsPerLiquidityCumulativeX128
|
||||
- beforeOrAt.secondsPerLiquidityCumulativeX128
|
||||
)
|
||||
* targetDelta) / observationTimeDelta
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function observe(
|
||||
Observation[65535] storage self,
|
||||
uint32 time,
|
||||
uint32[] memory secondsAgos,
|
||||
int24 tick,
|
||||
uint16 index,
|
||||
uint128 liquidity,
|
||||
uint16 cardinality
|
||||
) internal view returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) {
|
||||
require(cardinality > 0, "Oracle: NOT_INIT");
|
||||
|
||||
tickCumulatives = new int56[](secondsAgos.length);
|
||||
secondsPerLiquidityCumulativeX128s = new uint160[](secondsAgos.length);
|
||||
for (uint256 i = 0; i < secondsAgos.length; i++) {
|
||||
(tickCumulatives[i], secondsPerLiquidityCumulativeX128s[i]) =
|
||||
observeSingle(self, time, secondsAgos[i], tick, index, liquidity, cardinality);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user