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