Skip to content
Merged
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
24 changes: 24 additions & 0 deletions src/interfaces/IAlgebraPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/**
* @title The minimal interface we use for a Algebra Pool
* @dev The pool interface is broken up into many smaller pieces.
* Credit to Uniswap Labs under GPL-2.0-or-later license:
* https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
*/
interface IAlgebraPool {
/// @notice The first of the two tokens of the pool, sorted by address
/// @return The token contract address
function token0() external view returns (address);

/// @notice The second of the two tokens of the pool, sorted by address
/// @return The token contract address
function token1() external view returns (address);

function getTimepoints(uint32[] calldata secondsAgos)
external
view
returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s, uint112[] memory volatilityCumulatives, uint256[] memory volumePerAvgLiquiditys);

}
156 changes: 156 additions & 0 deletions src/oracles/AlgebraOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.13;

import {Owned} from "solmate/auth/Owned.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";

import {IOracle} from "../interfaces/IOracle.sol";

import {IAlgebraPool} from "../interfaces/IAlgebraPool.sol";
import {TickMath} from "v3-core/libraries/TickMath.sol";
import {FullMath} from "v3-core/libraries/FullMath.sol";

/// @title Oracle using Algebra TWAP oracle as data source
/// @notice The oracle contract that provides the current price to purchase
/// the underlying token while exercising options. Uses Algebra TWAP oracle
/// as data source, and then applies a multiplier & lower bound.
/// @notice Same as UniswapV3, with a minimal interface change.
/// (observe -> getTimepoints)
contract AlgebraOracle is IOracle, Owned {
/// -----------------------------------------------------------------------
/// Library usage
/// -----------------------------------------------------------------------

using FixedPointMathLib for uint256;

/// -----------------------------------------------------------------------
/// Errors
/// -----------------------------------------------------------------------

error AlgebraOracle__InvalidParams();
error AlgebraOracle__InvalidWindow();
error AlgebraOracle__BelowMinPrice();

/// -----------------------------------------------------------------------
/// Events
/// -----------------------------------------------------------------------

event SetParams(uint56 secs, uint56 ago, uint128 minPrice);

/// -----------------------------------------------------------------------
/// Immutable parameters
/// -----------------------------------------------------------------------

uint256 internal constant MIN_SECS = 20 minutes;

/// @notice The Algebra Pool contract (provides the oracle)
IAlgebraPool public immutable algebraPool;

/// -----------------------------------------------------------------------
/// Storage variables
/// -----------------------------------------------------------------------

/// @notice The size of the window to take the TWAP value over in seconds.
uint32 public secs;

/// @notice The number of seconds in the past to take the TWAP from. The window
/// would be (block.timestamp - secs - ago, block.timestamp - ago].
uint32 public ago;

/// @notice The minimum value returned by getPrice(). Maintains a floor for the
/// price to mitigate potential attacks on the TWAP oracle.
uint128 public minPrice;

/// @notice Whether the price of token0 should be returned (in units of token1).
/// If false, the price is returned in units of token0.
bool public isToken0;

/// -----------------------------------------------------------------------
/// Constructor
/// -----------------------------------------------------------------------

constructor(IAlgebraPool algebraPool_, address token, address owner_, uint32 secs_, uint32 ago_, uint128 minPrice_) Owned(owner_) {
if (algebraPool_.token0() != token && algebraPool_.token1() != token) revert AlgebraOracle__InvalidParams();
if (secs_ < MIN_SECS) revert AlgebraOracle__InvalidWindow();
algebraPool = algebraPool_;
isToken0 = token == algebraPool_.token0();
secs = secs_;
ago = ago_;
minPrice = minPrice_;

emit SetParams(secs_, ago_, minPrice_);
}

/// -----------------------------------------------------------------------
/// IOracle
/// -----------------------------------------------------------------------

/// @inheritdoc IOracle
function getPrice() external view override returns (uint256 price) {
/// -----------------------------------------------------------------------
/// Validation
/// -----------------------------------------------------------------------

// The Algebra pool reverts on invalid TWAP queries, so we don't need to

/// -----------------------------------------------------------------------
/// Computation
/// -----------------------------------------------------------------------

// query Algebra oracle to get TWAP tick
{
uint32 _twapDuration = secs;
uint32 _twapAgo = ago;
uint32[] memory secondsAgo = new uint32[](2);
secondsAgo[0] = _twapDuration + _twapAgo;
secondsAgo[1] = _twapAgo;

(int56[] memory tickCumulatives,,,) = algebraPool.getTimepoints(secondsAgo);
int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int56(int32(_twapDuration)));

uint256 decimalPrecision = 1e18;

// from https://optimistic.etherscan.io/address/0xB210CE856631EeEB767eFa666EC7C1C57738d438#code#F5#L49
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;
price = isToken0 ? FullMath.mulDiv(ratioX192, decimalPrecision, 1 << 192) : FullMath.mulDiv(1 << 192, decimalPrecision, ratioX192);
} else {
uint256 ratioX128 = FullMath.mulDiv(sqrtRatioX96, sqrtRatioX96, 1 << 64);
price = isToken0 ? FullMath.mulDiv(ratioX128, decimalPrecision, 1 << 128) : FullMath.mulDiv(1 << 128, decimalPrecision, ratioX128);
}
}

// apply minimum price
if (price < minPrice) revert AlgebraOracle__BelowMinPrice();
}

/// @inheritdoc IOracle
function getTokens() external view override returns (address paymentToken, address underlyingToken) {
if (isToken0) {
return (algebraPool.token1(), algebraPool.token0());
} else {
return (algebraPool.token0(), algebraPool.token1());
}
}

/// -----------------------------------------------------------------------
/// Owner functions
/// -----------------------------------------------------------------------

/// @notice Updates the oracle parameters. Only callable by the owner.
/// @param secs_ The size of the window to take the TWAP value over in seconds.
/// @param ago_ The number of seconds in the past to take the TWAP from. The window
/// would be (block.timestamp - secs - ago, block.timestamp - ago].
/// @param minPrice_ The minimum value returned by getPrice(). Maintains a floor for the
/// price to mitigate potential attacks on the TWAP oracle.
function setParams(uint32 secs_, uint32 ago_, uint128 minPrice_) external onlyOwner {
if (secs_ < MIN_SECS) revert AlgebraOracle__InvalidWindow();
secs = secs_;
ago = ago_;
minPrice = minPrice_;
emit SetParams(secs_, ago_, minPrice_);
}
}