-
Notifications
You must be signed in to change notification settings - Fork 80
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
Add AERO/WETH Oracle #2043
Add AERO/WETH Oracle #2043
Changes from all commits
b45a9e3
819ac07
205a80c
8d90aa7
f61b2c6
4357f8f
8668d69
52210f8
4167225
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "../interfaces/chainlink/AggregatorV3Interface.sol"; | ||
import { OracleRouterBase } from "./OracleRouterBase.sol"; | ||
import { StableMath } from "../utils/StableMath.sol"; | ||
|
||
// @notice Oracle Router that denominates all prices in ETH for base network | ||
contract BaseOETHOracleRouter is OracleRouterBase { | ||
using StableMath for uint256; | ||
|
||
address public immutable aeroPriceFeed; | ||
|
||
constructor(address _aeroPriceFeed) { | ||
aeroPriceFeed = _aeroPriceFeed; | ||
} | ||
|
||
/** | ||
* @notice Returns the total price in 18 digit units for a given asset. | ||
* This implementation does not (!) do range checks as the | ||
* parent OracleRouter does. | ||
* @param asset address of the asset | ||
* @return uint256 unit price for 1 asset unit, in 18 decimal fixed | ||
*/ | ||
function price(address asset) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is identical to the one in OETHOracleRouter.sol. Why not let your contract inherit from that one? A good rule of thumb is to try to never have copy/pasted - duplicated code in your repository. Because when there is a bug discovered you might fix it in 1 location but not the other. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we inherit from OETHOracleRouter, we might wanna initialize the parent contract by passing the Aura price feed address. Since it is designed to work with Base, it made more sense to inherit from the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yes you are right I haven't noticed that. What I'd do is create then another abstract contract called Then alter the code so that |
||
external | ||
view | ||
virtual | ||
override | ||
returns (uint256) | ||
{ | ||
(address _feed, uint256 maxStaleness) = feedMetadata(asset); | ||
if (_feed == FIXED_PRICE) { | ||
return 1e18; | ||
} | ||
require(_feed != address(0), "Asset not available"); | ||
|
||
// slither-disable-next-line unused-return | ||
(, int256 _iprice, , uint256 updatedAt, ) = AggregatorV3Interface(_feed) | ||
.latestRoundData(); | ||
|
||
require( | ||
updatedAt + maxStaleness >= block.timestamp, | ||
"Oracle price too old" | ||
); | ||
|
||
uint8 decimals = getDecimals(_feed); | ||
uint256 _price = uint256(_iprice).scaleBy(18, decimals); | ||
return _price; | ||
} | ||
|
||
/** | ||
* @dev The price feed contract to use for a particular asset along with | ||
* maximum data staleness | ||
* @param asset address of the asset | ||
* @return feedAddress address of the price feed for the asset | ||
* @return maxStaleness maximum acceptable data staleness duration | ||
*/ | ||
function feedMetadata(address asset) | ||
internal | ||
view | ||
virtual | ||
override | ||
returns (address feedAddress, uint256 maxStaleness) | ||
{ | ||
if (asset == 0x4200000000000000000000000000000000000006) { | ||
// FIXED_PRICE: WETH/ETH | ||
feedAddress = FIXED_PRICE; | ||
maxStaleness = 0; | ||
} else if (asset == 0x940181a94A35A4569E4529A3CDfB74e38FD98631) { | ||
// AERO/ETH | ||
feedAddress = aeroPriceFeed; | ||
maxStaleness = 1 days; | ||
} else { | ||
revert("Asset not available"); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import { AggregatorV3Interface } from "../interfaces/chainlink/AggregatorV3Interface.sol"; | ||
import { StableMath } from "../utils/StableMath.sol"; | ||
import "@openzeppelin/contracts/utils/math/Math.sol"; | ||
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; | ||
|
||
/** | ||
* @notice Price feed when the feed of 2 oracle prices need combining to achieve desired result. | ||
* | ||
* @dev multiplying oracle pair prices has combining properties. E.g. price FXS/USD multiplied by | ||
* USD/ETH results effectively in FXS/ETH price. Since oracle prices express asset on the left priced | ||
* by the asset on the right, we sometimes need to reverse prices in order to achieve desired results. | ||
* ETH/USD reversed is USD/ETH. | ||
* | ||
* In our first usage of this contract we required FXS/ETH price. It can be derived using FXS/USD and | ||
* ETH/USD prices. Since we need the latter reversed to get the desired result we configure the contract | ||
* by using FXS/USD as feed 0 and USD/ETH (reversed from ETH/USD) as feed 1. | ||
* | ||
* IMPORTANT: It is important to consider that combining 2 Oracle price feeds increases their | ||
* error rate using addition. Meaning if feed 0 has error rate of X and feed 1 error rate of Y | ||
* the resulting error rate is X + Y. E.g. | ||
* - FXS/ETH combines FXS/USD (possible deviation 2%), ETH/USD (possible deviation 0.5%) resulting in | ||
* FXS/USD having possible deviation of 2.5% | ||
*/ | ||
contract PriceFeedPair is AggregatorV3Interface { | ||
using SafeCast for uint256; | ||
using SafeCast for int256; | ||
using StableMath for uint256; | ||
|
||
// Fields to make it compatible with `AggregatorV3Interface` | ||
uint8 public constant override decimals = 18; | ||
string public constant override description = ""; | ||
uint256 public constant override version = 1; | ||
address public immutable addressFeed0; | ||
address public immutable addressFeed1; | ||
bool public immutable reverseFeed0; | ||
bool public immutable reverseFeed1; | ||
uint8 internal immutable decimalsFeed0; | ||
uint8 internal immutable decimalsFeed1; | ||
|
||
error PriceFeedAddressError(address _address); | ||
error PriceFeedsMatchError(); | ||
|
||
constructor( | ||
address _addressFeed0, | ||
address _addressFeed1, | ||
bool _reverseFeed0, | ||
bool _reverseFeed1 | ||
) { | ||
if (_addressFeed0 == address(0)) { | ||
revert PriceFeedAddressError(_addressFeed0); | ||
} | ||
if (_addressFeed1 == address(0)) { | ||
revert PriceFeedAddressError(_addressFeed1); | ||
} | ||
if (_addressFeed0 == _addressFeed1) { | ||
revert PriceFeedsMatchError(); | ||
} | ||
|
||
decimalsFeed0 = AggregatorV3Interface(_addressFeed0).decimals(); | ||
decimalsFeed1 = AggregatorV3Interface(_addressFeed1).decimals(); | ||
addressFeed0 = _addressFeed0; | ||
addressFeed1 = _addressFeed1; | ||
reverseFeed0 = _reverseFeed0; | ||
reverseFeed1 = _reverseFeed1; | ||
} | ||
|
||
function _calculatePrice(int256 priceFeed0, int256 priceFeed1) | ||
internal | ||
view | ||
returns (int256) | ||
{ | ||
uint256 price0 = priceFeed0.toUint256().scaleBy(18, decimalsFeed0); | ||
|
||
if (reverseFeed0) { | ||
price0 = uint256(1e18).divPrecisely(price0); | ||
} | ||
|
||
uint256 price1 = priceFeed1.toUint256().scaleBy(18, decimalsFeed1); | ||
|
||
if (reverseFeed1) { | ||
price1 = uint256(1e18).divPrecisely(price1); | ||
} | ||
|
||
return price0.mulTruncate(price1).toInt256(); | ||
} | ||
|
||
/** | ||
* @notice This function exists to make the contract compatible | ||
* with AggregatorV3Interface (which OETHOracleRouter uses to | ||
* get the price). | ||
**/ | ||
function latestRoundData() | ||
external | ||
view | ||
override | ||
returns ( | ||
uint80, | ||
int256 price, | ||
uint256, | ||
uint256 updatedAt, | ||
uint80 | ||
) | ||
{ | ||
// slither-disable-next-line unused-return | ||
(, int256 _price0, , uint256 updatedAt0, ) = AggregatorV3Interface( | ||
addressFeed0 | ||
).latestRoundData(); | ||
// slither-disable-next-line unused-return | ||
(, int256 _price1, , uint256 updatedAt1, ) = AggregatorV3Interface( | ||
addressFeed1 | ||
).latestRoundData(); | ||
updatedAt = Math.min(updatedAt0, updatedAt1); | ||
price = _calculatePrice(_price0, _price1); | ||
} | ||
|
||
/** | ||
* @notice This function exists to make the contract compatible | ||
* with AggregatorV3Interface. The two oracles don't have rounds | ||
* in sync and for that reason we can not query arbitrary oracle | ||
* round and combine it. | ||
**/ | ||
function getRoundData(uint80) | ||
external | ||
pure | ||
override | ||
returns ( | ||
uint80, | ||
int256 price, | ||
uint256, | ||
uint256 updatedAt, | ||
uint80 | ||
) | ||
{ | ||
revert("No data present"); | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this particular case the word "Base" can be confusing since we are already using it in
OracleRouterBase
to communicate the abstract classes shared by many routers. Maybe aBaseChainOETHOracleRouter
would be e better name