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
17 changes: 17 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,20 @@ jobs:
- run: yarn test:registries
env:
NODE_OPTIONS: '--max-old-space-size=32768'

oracles-tests:
name: 'Oracle Factory Tests'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'yarn'
- run: yarn install --immutable
- run: yarn test:oracles
env:
NODE_OPTIONS: '--max-old-space-size=32768'
TS_NODE_SKIP_IGNORE: true
MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
FORK_NETWORK: mainnet
10 changes: 8 additions & 2 deletions common/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export interface ITokens {
sUSD?: string
FRAX?: string
MIM?: string
eUSD?: string
crvUSD?: string
aDAI?: string
aUSDC?: string
Expand Down Expand Up @@ -61,7 +60,6 @@ export interface ITokens {
CVX?: string
SDT?: string
USDCPLUS?: string
ETHPLUS?: string
ankrETH?: string
frxETH?: string
sfrxETH?: string
Expand Down Expand Up @@ -135,6 +133,12 @@ export interface ITokens {
// Sky
USDS?: string
sUSDS?: string

// RTokens
eUSD?: string
ETHPLUS?: string
bsdETH?: string
KNOX?: string
}

export type ITokensKeys = Array<keyof ITokens>
Expand Down Expand Up @@ -560,6 +564,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
cbBTC: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf',
WELL: '0xA88594D404727625A9437C3f886C7643872296AE',
DEGEN: '0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed',
bsdETH: '0xcb327b99ff831bf8223cced12b1338ff3aa322ff',
},
chainlinkFeeds: {
DAI: '0x591e79239a7d679378ec8c847e5038150364c78f', // 0.3%, 24hr
Expand Down Expand Up @@ -616,6 +621,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
saArbUSDT: '', // TODO our wrapper. remove from deployment script after placing here
USDM: '0x59d9356e565ab3a36dd77763fc0d87feaf85508c',
wUSDM: '0x57f5e098cad7a3d1eed53991d4d66c45c9af7812',
KNOX: '0x0bbf664d46becc28593368c97236faa0fb397595',
},
chainlinkFeeds: {
ARB: '0xb2A824043730FE05F3DA2efaFa1CBbe83fa548D6',
Expand Down
48 changes: 0 additions & 48 deletions contracts/facade/factories/CurveOracleFactory.sol

This file was deleted.

103 changes: 103 additions & 0 deletions contracts/facade/oracles/ExchangeRateOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// 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";
import { IAsset } from "../../interfaces/IAsset.sol";
import { IRToken } from "../../interfaces/IRToken.sol";

/**
* @title ExchangeRateOracle
* @notice An immutable Exchange Rate Oracle for an RToken (eg: ETH+/ETH)
*
* ::Notice::
* The oracle does not call refresh() on the RToken or the underlying assets, so the price can be
* stale. This is generally not an issue for active RTokens as they are refreshed often by other
* protocol operations, however do keep this in mind when using this for low-activity RTokens.
*
* If you need the freshest possible price, consider using RTokenAsset.latestPrice() instead,
* however it is a mutator function instead of a view-only function hence not compatible with
* Chainlink style interfaces.
*
* ::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:
* `basketHandler.status() == 0 && basketHandler.fullyCollateralized()`
* where `basketHandler` can be safely cached from `rToken.main().basketHandler()`.
*
* 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 ZeroAddress();

IRToken public immutable rToken;
uint256 public constant override version = 1;

Check warning on line 38 in contracts/facade/oracles/ExchangeRateOracle.sol

View workflow job for this annotation

GitHub Actions / Lint Checks

Constant name must be in capitalized SNAKE_CASE

constructor(address _rToken) {
if (_rToken == address(0)) {
revert ZeroAddress();
}

rToken = IRToken(_rToken);
}

function decimals() external pure override returns (uint8) {
return 18;
}

function description() external view override returns (string memory) {
return string.concat(rToken.symbol(), " Exchange Rate Oracle");
}

function exchangeRate() public view returns (uint256) {
uint256 supply = IRToken(rToken).totalSupply();
if (supply == 0) {
return FIX_ONE;
}

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

/**
* @dev Ignores roundId completely, prefer using latestRoundData()
*/
function getRoundData(uint80)
external
view
override
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
return this.latestRoundData();
}

function latestRoundData()
external
view
override
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
return (
uint80(block.number),
int256(exchangeRate()),
block.timestamp - 1,
block.timestamp,
uint80(block.number)
);
}
}
8 changes: 8 additions & 0 deletions contracts/facade/oracles/IExchangeRateOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

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

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

/**
* @title OracleFactory
* @notice An immutable factory for RToken Exchange Rate Oracles
*/
contract OracleFactory {
struct Oracles {
ExchangeRateOracle exchangeRateOracle;
ReferenceRateOracle referenceRateOracle;
}

error OracleAlreadyDeployed(address rToken);

event OracleDeployed(address indexed rToken, Oracles oracles);

// {rtoken} => {oracle}
mapping(address => Oracles) public oracleRegistry;

/// @param rToken The RToken to deploy oracles for
function deployOracle(address rToken) external returns (Oracles memory oracles) {
if (
rToken == address(0) || address(oracleRegistry[rToken].exchangeRateOracle) != address(0)
) {
revert OracleAlreadyDeployed(rToken);
}

ExchangeRateOracle eOracle = new ExchangeRateOracle(rToken);
ReferenceRateOracle rOracle = new ReferenceRateOracle(rToken);

oracles = Oracles({ exchangeRateOracle: eOracle, referenceRateOracle: rOracle });

eOracle.latestRoundData();
rOracle.latestRoundData();

oracleRegistry[rToken] = oracles;
emit OracleDeployed(rToken, oracles);
}
}
Loading
Loading