forked from mico/idle_moloch
181 lines
9.1 KiB
Solidity
181 lines
9.1 KiB
Solidity
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
pragma solidity >=0.5.0 <0.8.0;
|
|
|
|
import '@uniswap/v3-core/contracts/libraries/FullMath.sol';
|
|
import '@uniswap/v3-core/contracts/libraries/TickMath.sol';
|
|
import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
|
|
|
|
/// @title Oracle library
|
|
/// @notice Provides functions to integrate with V3 pool oracle
|
|
library OracleLibrary {
|
|
/// @notice Calculates time-weighted means of tick and liquidity for a given Uniswap V3 pool
|
|
/// @param pool Address of the pool that we want to observe
|
|
/// @param secondsAgo Number of seconds in the past from which to calculate the time-weighted means
|
|
/// @return arithmeticMeanTick The arithmetic mean tick from (block.timestamp - secondsAgo) to block.timestamp
|
|
/// @return harmonicMeanLiquidity The harmonic mean liquidity from (block.timestamp - secondsAgo) to block.timestamp
|
|
function consult(address pool, uint32 secondsAgo)
|
|
internal
|
|
view
|
|
returns (int24 arithmeticMeanTick, uint128 harmonicMeanLiquidity)
|
|
{
|
|
require(secondsAgo != 0, 'BP');
|
|
|
|
uint32[] memory secondsAgos = new uint32[](2);
|
|
secondsAgos[0] = secondsAgo;
|
|
secondsAgos[1] = 0;
|
|
|
|
(int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) =
|
|
IUniswapV3Pool(pool).observe(secondsAgos);
|
|
|
|
int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
|
|
uint160 secondsPerLiquidityCumulativesDelta =
|
|
secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0];
|
|
|
|
arithmeticMeanTick = int24(tickCumulativesDelta / secondsAgo);
|
|
// Always round to negative infinity
|
|
if (tickCumulativesDelta < 0 && (tickCumulativesDelta % secondsAgo != 0)) arithmeticMeanTick--;
|
|
|
|
// We are multiplying here instead of shifting to ensure that harmonicMeanLiquidity doesn't overflow uint128
|
|
uint192 secondsAgoX160 = uint192(secondsAgo) * type(uint160).max;
|
|
harmonicMeanLiquidity = uint128(secondsAgoX160 / (uint192(secondsPerLiquidityCumulativesDelta) << 32));
|
|
}
|
|
|
|
/// @notice Given a tick and a token amount, calculates the amount of token received in exchange
|
|
/// @param tick Tick value used to calculate the quote
|
|
/// @param baseAmount Amount of token to be converted
|
|
/// @param baseToken Address of an ERC20 token contract used as the baseAmount denomination
|
|
/// @param quoteToken Address of an ERC20 token contract used as the quoteAmount denomination
|
|
/// @return quoteAmount Amount of quoteToken received for baseAmount of baseToken
|
|
function getQuoteAtTick(
|
|
int24 tick,
|
|
uint128 baseAmount,
|
|
address baseToken,
|
|
address quoteToken
|
|
) internal pure returns (uint256 quoteAmount) {
|
|
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick);
|
|
|
|
// Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself
|
|
if (sqrtRatioX96 <= type(uint128).max) {
|
|
uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96;
|
|
quoteAmount = baseToken < quoteToken
|
|
? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192)
|
|
: FullMath.mulDiv(1 << 192, baseAmount, ratioX192);
|
|
} else {
|
|
uint256 ratioX128 = FullMath.mulDiv(sqrtRatioX96, sqrtRatioX96, 1 << 64);
|
|
quoteAmount = baseToken < quoteToken
|
|
? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128)
|
|
: FullMath.mulDiv(1 << 128, baseAmount, ratioX128);
|
|
}
|
|
}
|
|
|
|
/// @notice Given a pool, it returns the number of seconds ago of the oldest stored observation
|
|
/// @param pool Address of Uniswap V3 pool that we want to observe
|
|
/// @return secondsAgo The number of seconds ago of the oldest observation stored for the pool
|
|
function getOldestObservationSecondsAgo(address pool) internal view returns (uint32 secondsAgo) {
|
|
(, , uint16 observationIndex, uint16 observationCardinality, , , ) = IUniswapV3Pool(pool).slot0();
|
|
require(observationCardinality > 0, 'NI');
|
|
|
|
(uint32 observationTimestamp, , , bool initialized) =
|
|
IUniswapV3Pool(pool).observations((observationIndex + 1) % observationCardinality);
|
|
|
|
// The next index might not be initialized if the cardinality is in the process of increasing
|
|
// In this case the oldest observation is always in index 0
|
|
if (!initialized) {
|
|
(observationTimestamp, , , ) = IUniswapV3Pool(pool).observations(0);
|
|
}
|
|
|
|
secondsAgo = uint32(block.timestamp) - observationTimestamp;
|
|
}
|
|
|
|
/// @notice Given a pool, it returns the tick value as of the start of the current block
|
|
/// @param pool Address of Uniswap V3 pool
|
|
/// @return The tick that the pool was in at the start of the current block
|
|
function getBlockStartingTickAndLiquidity(address pool) internal view returns (int24, uint128) {
|
|
(, int24 tick, uint16 observationIndex, uint16 observationCardinality, , , ) = IUniswapV3Pool(pool).slot0();
|
|
|
|
// 2 observations are needed to reliably calculate the block starting tick
|
|
require(observationCardinality > 1, 'NEO');
|
|
|
|
// If the latest observation occurred in the past, then no tick-changing trades have happened in this block
|
|
// therefore the tick in `slot0` is the same as at the beginning of the current block.
|
|
// We don't need to check if this observation is initialized - it is guaranteed to be.
|
|
(uint32 observationTimestamp, int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128, ) =
|
|
IUniswapV3Pool(pool).observations(observationIndex);
|
|
if (observationTimestamp != uint32(block.timestamp)) {
|
|
return (tick, IUniswapV3Pool(pool).liquidity());
|
|
}
|
|
|
|
uint256 prevIndex = (uint256(observationIndex) + observationCardinality - 1) % observationCardinality;
|
|
(
|
|
uint32 prevObservationTimestamp,
|
|
int56 prevTickCumulative,
|
|
uint160 prevSecondsPerLiquidityCumulativeX128,
|
|
bool prevInitialized
|
|
) = IUniswapV3Pool(pool).observations(prevIndex);
|
|
|
|
require(prevInitialized, 'ONI');
|
|
|
|
uint32 delta = observationTimestamp - prevObservationTimestamp;
|
|
tick = int24((tickCumulative - prevTickCumulative) / delta);
|
|
uint128 liquidity =
|
|
uint128(
|
|
(uint192(delta) * type(uint160).max) /
|
|
(uint192(secondsPerLiquidityCumulativeX128 - prevSecondsPerLiquidityCumulativeX128) << 32)
|
|
);
|
|
return (tick, liquidity);
|
|
}
|
|
|
|
/// @notice Information for calculating a weighted arithmetic mean tick
|
|
struct WeightedTickData {
|
|
int24 tick;
|
|
uint128 weight;
|
|
}
|
|
|
|
/// @notice Given an array of ticks and weights, calculates the weighted arithmetic mean tick
|
|
/// @param weightedTickData An array of ticks and weights
|
|
/// @return weightedArithmeticMeanTick The weighted arithmetic mean tick
|
|
/// @dev Each entry of `weightedTickData` should represents ticks from pools with the same underlying pool tokens. If they do not,
|
|
/// extreme care must be taken to ensure that ticks are comparable (including decimal differences).
|
|
/// @dev Note that the weighted arithmetic mean tick corresponds to the weighted geometric mean price.
|
|
function getWeightedArithmeticMeanTick(WeightedTickData[] memory weightedTickData)
|
|
internal
|
|
pure
|
|
returns (int24 weightedArithmeticMeanTick)
|
|
{
|
|
// Accumulates the sum of products between each tick and its weight
|
|
int256 numerator;
|
|
|
|
// Accumulates the sum of the weights
|
|
uint256 denominator;
|
|
|
|
// Products fit in 152 bits, so it would take an array of length ~2**104 to overflow this logic
|
|
for (uint256 i; i < weightedTickData.length; i++) {
|
|
numerator += weightedTickData[i].tick * int256(weightedTickData[i].weight);
|
|
denominator += weightedTickData[i].weight;
|
|
}
|
|
|
|
weightedArithmeticMeanTick = int24(numerator / int256(denominator));
|
|
// Always round to negative infinity
|
|
if (numerator < 0 && (numerator % int256(denominator) != 0)) weightedArithmeticMeanTick--;
|
|
}
|
|
|
|
/// @notice Returns the "synthetic" tick which represents the price of the first entry in `tokens` in terms of the last
|
|
/// @dev Useful for calculating relative prices along routes.
|
|
/// @dev There must be one tick for each pairwise set of tokens.
|
|
/// @param tokens The token contract addresses
|
|
/// @param ticks The ticks, representing the price of each token pair in `tokens`
|
|
/// @return syntheticTick The synthetic tick, representing the relative price of the outermost tokens in `tokens`
|
|
function getChainedPrice(address[] memory tokens, int24[] memory ticks)
|
|
internal
|
|
pure
|
|
returns (int256 syntheticTick)
|
|
{
|
|
require(tokens.length - 1 == ticks.length, 'DL');
|
|
for (uint256 i = 1; i <= ticks.length; i++) {
|
|
// check the tokens for address sort order, then accumulate the
|
|
// ticks into the running synthetic tick, ensuring that intermediate tokens "cancel out"
|
|
tokens[i - 1] < tokens[i] ? syntheticTick += ticks[i - 1] : syntheticTick -= ticks[i - 1];
|
|
}
|
|
}
|
|
}
|