Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
48 changes: 0 additions & 48 deletions contracts/facade/factories/CurveOracleFactory.sol

This file was deleted.

101 changes: 101 additions & 0 deletions contracts/facade/oracles/curve-oracle/CurveOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import { IExchangeRateOracle } from "../exchange-rate/IExchangeRateOracle.sol";

interface ICurveStableSwapNG {
function coins(uint256 i) external view returns (address);

function get_virtual_price() external view returns (uint256);

function stored_rates() external view returns (uint256[] memory);
}

/**
* @title CurveOracle
* @notice An immutable Exchange Rate Oracle for a StableSwapNG Curve LP Token,
* with one or more appreciating assets. Only for 2-asset Curve LP Tokens.
* @dev Does not account for native asset appreciation, only accounts for the
* appreciation in the Curve LP via trading volume.
*
* The oracles specified for the pool MUST be for the base unit, for example
* if the paired token is sDAI, you'd specify the oracle for DAI/USD.
*/
contract CurveOracle {
enum OracleType {
STORED,
STATIC,
RTOKEN,
CHAINLINK
}

struct OracleConfig {
OracleType oracleType;
address rateProvider;
uint256 staticValue;
uint256 timeout;
}

error BadOracleValue();
error InvalidOracleType();

ICurveStableSwapNG public immutable curvePool;
OracleConfig public oracleConfig0;
OracleConfig public oracleConfig1;

constructor(
address _curvePool,
OracleConfig memory _oracleConfig0,
OracleConfig memory _oracleConfig1
) {
curvePool = ICurveStableSwapNG(_curvePool);
oracleConfig0 = _oracleConfig0;
oracleConfig1 = _oracleConfig1;
}

function _getTokenPrice(uint256 tokenId) internal view virtual returns (uint256) {
OracleConfig memory oracleConfig = tokenId == 0 ? oracleConfig0 : oracleConfig1;
OracleType oracleType = oracleConfig.oracleType;

if (oracleType == OracleType.STORED) {
return curvePool.stored_rates()[tokenId];
} else if (oracleType == OracleType.STATIC) {
return oracleConfig.staticValue;
} else if (oracleType == OracleType.RTOKEN) {
return IExchangeRateOracle(oracleConfig.rateProvider).exchangeRate();
} else if (oracleType == OracleType.CHAINLINK) {
AggregatorV3Interface oracle = AggregatorV3Interface(oracleConfig.rateProvider);
uint8 decimals = oracle.decimals();
(, int256 price, , uint256 updateTime, ) = oracle.latestRoundData();

if (price < 0) {
revert BadOracleValue();
}

if (block.timestamp - updateTime > oracleConfig.timeout) {
Comment thread
akshatmittal marked this conversation as resolved.
Outdated
revert BadOracleValue();
}

if (decimals == 18) {
return uint256(price);
} else if (decimals < 18) {
return uint256(price) * (10**(18 - decimals));
} else {
return uint256(price) / (10**(decimals - 18));
}
}

revert InvalidOracleType();
}

function getPrice() public view virtual returns (uint256) {
uint256 token0Price = _getTokenPrice(0);
uint256 token1Price = _getTokenPrice(1);

uint256 minPrice = token0Price < token1Price ? token0Price : token1Price;
uint256 virtualPrice = curvePool.get_virtual_price();

return (virtualPrice * minPrice) / 1e18;
}
}
74 changes: 74 additions & 0 deletions contracts/facade/oracles/exchange-rate/ExchangeRateOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import { FIX_ONE, divuu } from "../../../libraries/Fixed.sol";
import { IExchangeRateOracle } from "./IExchangeRateOracle.sol";

interface IMinimalRToken {
function basketsNeeded() external view returns (uint192);

function totalSupply() external view returns (uint256);
}

/**
* @title ExchangeRateOracle
* @notice An immutable Exchange Rate Oracle for an RToken
*
* ::Warning:: In the event of an RToken taking a loss in excess of the StRSR overcollateralization
* layer, the devaluation will not be reflected until the RToken is done trading. This causes
* the exchange rate to be too high during the rebalancing phase. If the exchange rate is relied
* upon naively, then it could be misleading.
*
* As a consumer of this oracle, you may want to guard against this case by monitoring:
* `rToken.status() == 0 && rToken.fullyCollateralized()`
*
* However, note that `fullyCollateralized()` is extremely gas-costly. We recommend executing
* the function off-chain. `status()` is cheap and more reasonable to be called on-chain.
*/
contract ExchangeRateOracle is IExchangeRateOracle {
error MissingRToken();

address public immutable rToken;

constructor(address _rToken) {
// allow address(0)
rToken = _rToken;
}

function exchangeRate() public view returns (uint256) {
if (rToken == address(0)) {
revert MissingRToken();
}

uint256 supply = IMinimalRToken(rToken).totalSupply();
if (supply == 0) {
return FIX_ONE;
}

return divuu(uint256(IMinimalRToken(rToken).basketsNeeded()), supply);
}

function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
return (
uint80(block.timestamp),
Comment thread
akshatmittal marked this conversation as resolved.
Outdated
int256(exchangeRate()),
block.timestamp - 1,
block.timestamp,
uint80(block.timestamp)
);
}

function decimals() external pure returns (uint8) {
return 18; // RToken is always 18 decimals
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import { ExchangeRateOracle } from "./ExchangeRateOracle.sol";

/**
* @title ExchangeRateOracleFactory
* @notice An immutable factory for RToken Exchange Rate Oracles
*/
contract ExchangeRateOracleFactory {
error OracleAlreadyDeployed(address oracle);

event OracleDeployed(address indexed rToken, address indexed oracle);

// {rtoken} => {oracle}
mapping(address => ExchangeRateOracle) public oracles;

function deployOracle(address rToken) external returns (address) {
if (address(oracles[rToken]) != address(0)) {
revert OracleAlreadyDeployed(address(oracles[rToken]));
}

ExchangeRateOracle oracle = new ExchangeRateOracle(rToken);

if (rToken != address(0)) {
oracle.exchangeRate();
oracle.latestRoundData();
oracle.decimals();
}

oracles[rToken] = oracle;
emit OracleDeployed(address(rToken), address(oracle));

return address(oracle);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

interface IExchangeRateOracle {
function exchangeRate() external view returns (uint256);
}
32 changes: 32 additions & 0 deletions contracts/facade/oracles/yearn-curve-oracle/YearnCurveOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import { CurveOracle } from "../curve-oracle/CurveOracle.sol";

interface YearnVault {
function pricePerShare() external view returns (uint256);
}

/**
* @title YearnCurveOracle
* @notice An immutable Exchange Rate Oracle for a Yearn Vault containing a Curve LP Token,
* with one or more appreciating assets. Only for 2-asset Curve LP Tokens.
*/
contract YearnCurveOracle is CurveOracle {
YearnVault public immutable yearnVault;

constructor(
address _yearnVault,
address _curvePool,
OracleConfig memory _oracleConfig0,
OracleConfig memory _oracleConfig1
) CurveOracle(_curvePool, _oracleConfig0, _oracleConfig1) {
yearnVault = YearnVault(_yearnVault);
}

function getPrice() public view virtual override returns (uint256) {
uint256 pricePerShare = yearnVault.pricePerShare();

return (CurveOracle.getPrice() * pricePerShare) / 1e18;
}
}
56 changes: 0 additions & 56 deletions tasks/deployment/create-curve-oracle-factory.ts

This file was deleted.

Loading