-
Notifications
You must be signed in to change notification settings - Fork 4
/
ChainlinkUsdOracle.sol
121 lines (97 loc) · 5.57 KB
/
ChainlinkUsdOracle.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*//////////////////////////////////////////////////////////////
ChainlinkUsdOracle
//////////////////////////////////////////////////////////////*/
import { IOracle } from "../interfaces/IOracle.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
interface IAggegregatorV3 {
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
function decimals() external view returns (uint256);
}
/// @title ChainlinkUsdOracle
/// @notice Oracle implementation to price assets using USD-denominated chainlink feeds
contract ChainlinkUsdOracle is Ownable, IOracle {
using Math for uint256;
/// @dev internal alias for native ETH
address private constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @notice L2 sequencer uptime grace period during which prices are treated as stale
uint256 public constant SEQ_GRACE_PERIOD = 3600; // 1 hour
/// @notice Chainlink arbitrum sequencer uptime feed
IAggegregatorV3 public immutable ARB_SEQ_FEED;
/// @notice Chainlink ETH/USD price feed
IAggegregatorV3 public immutable ETH_USD_FEED;
/// @notice Fetch the ETH-denominated price feed associated with a given asset
/// @dev returns address(0) if there is no associated feed
mapping(address asset => address feed) public priceFeedFor;
/// @notice Prices older than the stale price threshold are considered invalid
mapping(address feed => uint256 stalePriceThreshold) public stalePriceThresholdFor;
/// @notice New Usd-denomiated chainlink feed has been associated with an asset
event FeedSet(address indexed asset, address feed);
/// @notice L2 sequencer is experiencing downtime
error ChainlinkUsdOracle_SequencerDown();
/// @notice L2 Sequencer has recently recovered from downtime and is in its grace period
error ChainlinkUsdOracle_GracePeriodNotOver();
/// @notice Last price update for `asset` was before the accepted stale price threshold
error ChainlinkUsdOracle_StalePrice(address asset);
/// @notice Latest price update for `asset` has a negative value
error ChainlinkUsdOracle_NonPositivePrice(address asset);
/// @notice Invalid oracle update round
error ChainlinkUsdOracle_InvalidRound();
/// @param owner Oracle owner address
/// @param arbSeqFeed Chainlink arbitrum sequencer feed
/// @param ethUsdFeed Chainlink ETH/USD price feed
/// @param ethUsdThreshold Stale price threshold for ETH/USD feed
constructor(address owner, address arbSeqFeed, address ethUsdFeed, uint256 ethUsdThreshold) Ownable() {
ARB_SEQ_FEED = IAggegregatorV3(arbSeqFeed);
ETH_USD_FEED = IAggegregatorV3(ethUsdFeed);
priceFeedFor[ETH] = ethUsdFeed;
stalePriceThresholdFor[ETH] = ethUsdThreshold;
_transferOwnership(owner);
}
/// @notice Compute the equivalent ETH value for a given amount of a particular asset
/// @param asset Address of the asset to be priced
/// @param amt Amount of the given asset to be priced
function getValueInEth(address asset, uint256 amt) external view returns (uint256) {
_checkSequencerFeed();
uint256 ethUsdPrice = _getPriceWithSanityChecks(ETH);
uint256 assetUsdPrice = _getPriceWithSanityChecks(asset);
uint256 decimals = IERC20Metadata(asset).decimals();
// [ROUND] price is rounded down. this is used for both debt and asset math, no effect
if (decimals <= 18) return (amt * 10 ** (18 - decimals)).mulDiv(uint256(assetUsdPrice), uint256(ethUsdPrice));
else return (amt / (10 ** decimals - 18)).mulDiv(uint256(assetUsdPrice), uint256(ethUsdPrice));
}
/// @notice Set Chainlink ETH-denominated feed for an asset
/// @param asset Address of asset to be priced
/// @param feed Address of the asset/eth chainlink feed
/// @param stalePriceThreshold prices older than this duration are considered invalid, denominated in seconds
/// @dev stalePriceThreshold must be equal or greater to the feed's heartbeat
function setFeed(address asset, address feed, uint256 stalePriceThreshold) external onlyOwner {
assert(IAggegregatorV3(feed).decimals() == 8);
priceFeedFor[asset] = feed;
stalePriceThresholdFor[feed] = stalePriceThreshold;
emit FeedSet(asset, feed);
}
/// @dev Check L2 sequencer health
function _checkSequencerFeed() private view {
(, int256 answer, uint256 startedAt,,) = ARB_SEQ_FEED.latestRoundData();
// answer == 0 -> sequncer up
// answer == 1 -> sequencer down
if (answer != 0) revert ChainlinkUsdOracle_SequencerDown();
if (startedAt == 0) revert ChainlinkUsdOracle_InvalidRound();
if (block.timestamp - startedAt <= SEQ_GRACE_PERIOD) revert ChainlinkUsdOracle_GracePeriodNotOver();
}
/// @dev Fetch price from chainlink feed with sanity checks
function _getPriceWithSanityChecks(address asset) private view returns (uint256) {
address feed = priceFeedFor[asset];
(, int256 price,, uint256 updatedAt,) = IAggegregatorV3(feed).latestRoundData();
if (price <= 0) revert ChainlinkUsdOracle_NonPositivePrice(asset);
if (updatedAt < block.timestamp - stalePriceThresholdFor[feed]) revert ChainlinkUsdOracle_StalePrice(asset);
return uint256(price);
}
}