Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opyn Crab Strategy PoC #237

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions contracts/lib/uniswap/OracleLibrary.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0 <0.9.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 / int56(uint56(secondsAgo)));
// Always round to negative infinity
if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int56(uint56(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);
}

unchecked {
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 - int56(uint56(prevTickCumulative))) / int56(uint56(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(uint256(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];
}
}
}
Loading