Skip to content

Feat: rigoblock oracle #91

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 3 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
branch = release-v4.9

[submodule "lib/pyth-sdk-solidity"]
path = lib/pyth-sdk-solidity
url = https://github.com/pyth-network/pyth-sdk-solidity
Expand All @@ -29,3 +28,6 @@
[submodule "lib/pendle-core-v2-public"]
path = lib/pendle-core-v2-public
url = https://github.com/pendle-finance/pendle-core-v2-public
[submodule "lib/v4-core"]
path = lib/v4-core
url = https://github.com/Uniswap/v4-core.git
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ An adapter's parameters and acceptance logic are easily observed on-chain.
| [PendleOracle](src/adapter/pendle/PendleOracle.sol) | Onchain | TWAP | Pendle markets | pendle market, twap window |
| [RateProviderOracle](src/adapter/rate/RateProviderOracle.sol) | Onchain | Rate | Balancer rate providers | rate provider |
| [FixedRateOracle](src/adapter/fixed/FixedRateOracle.sol) | Onchain | Rate | Any | rate |
| [RigoblockOracle](src/adapter/rigoblock/RigoblockOracle.sol) | Onchain | TWAP | UniV4 pools | twap window |

## Usage

Expand Down
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ src = "src"
out = "out"
test = "test"
libs = ["lib"]
solc = "0.8.23"
solc = "0.8.24"
evm_version = "cancun"
optimizer = true
optimizer_runs = 100_000
Expand Down
1 change: 1 addition & 0 deletions lib/v4-core
Submodule v4-core added at 59d3ec
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
@openzeppelin/contracts=lib/openzeppelin-contracts/contracts/
@pyth/=lib/pyth-sdk-solidity/
ethereum-vault-connector/=lib/ethereum-vault-connector/src/
@pendle/core-v2/=lib/pendle-core-v2-public/contracts/
@pendle/core-v2/=lib/pendle-core-v2-public/contracts/
@uniswap/v4-core/=lib/v4-core/
22 changes: 22 additions & 0 deletions src/adapter/rigoblock/IOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0 <0.9.0;

import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";

interface IOracle {
/// @member index The index of the last written observation for the pool
/// @member cardinality The cardinality of the observations array for the pool
/// @member cardinalityNext The cardinality target of the observations array for the pool, which will replace cardinality when enough observations are written
struct ObservationState {
uint16 index;
uint16 cardinality;
uint16 cardinalityNext;
}

function getState(PoolKey calldata key) external view returns (ObservationState memory state);

function observe(
PoolKey calldata key,
uint32[] calldata secondsAgos
) external view returns (int48[] memory tickCumulatives, uint144[] memory secondsPerLiquidityCumulativeX128s);
}
91 changes: 91 additions & 0 deletions src/adapter/rigoblock/RigoblockOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {OracleLibrary} from "@uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {BaseAdapter, Errors, IPriceOracle} from "../BaseAdapter.sol";
import {IOracle} from "./IOracle.sol";

/// @title RigoblockOracle
/// @custom:security-contact security@rigoblock.com
/// @author Rigoblock (https://rigoblock.com/)
/// @notice Adapter for Rigoblock's Uniswap V4 TWAP oracle hook.
/// @dev This oracle supports quoting tokenA/tokenB and tokenB/tokenA of the given pool.
/// WARNING: READ THIS BEFORE DEPLOYING
/// The cardinality of the observation buffer must be grown sufficiently to accommodate for the chosen TWAP window.
/// The observation buffer must contain enough observations to accommodate for the chosen TWAP window.
/// The chosen pool must have enough total liquidity to resist manipulation.
/// The chosen pool must have had sufficient liquidity when past observations were recorded in the buffer.
contract RigoblockOracle is BaseAdapter {
/// @dev The minimum length of the TWAP window.
uint32 internal constant MIN_TWAP_WINDOW = 5 minutes;
/// @inheritdoc IPriceOracle
string public constant name = "RigoblockOracle";
/// @notice One of the tokens in the pool.
address public immutable tokenA;
/// @notice The other token in the pool.
address public immutable tokenB;
/// @notice The desired length of the twap window.
uint32 public immutable twapWindow;
/// @notice The address of the BackGeoOracle hook.
address public immutable backGeoOracle;

/// @notice Deploy a UniswapV3Oracle.
/// @dev The oracle will support tokenA/tokenB and tokenB/tokenA pricing.
/// @param _tokenA One of the tokens in the pool.
/// @param _tokenB The other token in the pool.
/// @param _twapWindow The desired length of the twap window.
/// @param _backGeoOracle The address of the Uniswap V4 BackGeoOracle oracle hook.
constructor(address _tokenA, address _tokenB, uint32 _twapWindow, address _backGeoOracle) {
if (_twapWindow < MIN_TWAP_WINDOW || _twapWindow > uint32(type(int32).max)) {
revert Errors.PriceOracle_InvalidConfiguration();
}
tokenA = _tokenA;
tokenB = _tokenB;
twapWindow = _twapWindow;
backGeoOracle = _backGeoOracle;
PoolKey memory key = PoolKey({
currency0: Currency.wrap(tokenA),
currency1: Currency.wrap(tokenB),
fee: 0,
tickSpacing: TickMath.MAX_TICK_SPACING,
hooks: IHooks(backGeoOracle)
});
IOracle.ObservationState memory state = IOracle(backGeoOracle).getState(key);
if (state.cardinality == 0) revert Errors.PriceOracle_InvalidConfiguration();
}

/// @notice Get a quote by calling the pool's TWAP oracle.
/// @param inAmount The amount of `base` to convert.
/// @param base The token that is being priced. Either `tokenA` or `tokenB`.
/// @param quote The token that is the unit of account. Either `tokenB` or `tokenA`.
/// @return The converted amount.
function _getQuote(uint256 inAmount, address base, address quote) internal view override returns (uint256) {
if (!((base == tokenA && quote == tokenB) || (base == tokenB && quote == tokenA))) {
revert Errors.PriceOracle_NotSupported(base, quote);
}
// Size limitation enforced by the pool.
if (inAmount > type(uint128).max) revert Errors.PriceOracle_Overflow();

uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = twapWindow;

PoolKey memory key = PoolKey({
currency0: Currency.wrap(tokenA),
currency1: Currency.wrap(tokenB),
fee: 0,
tickSpacing: TickMath.MAX_TICK_SPACING,
hooks: IHooks(backGeoOracle)
});

// Calculate the mean tick over the twap window.
(int48[] memory tickCumulatives,) = IOracle(backGeoOracle).observe(key, secondsAgos);
int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
int24 tick = int24(tickCumulativesDelta / int56(uint56(twapWindow)));
if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int56(uint56(twapWindow)) != 0)) tick--;
return OracleLibrary.getQuoteAtTick(tick, uint128(inAmount), base, quote);
}
}