This repository has been archived by the owner on Jan 7, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Oracle.sol
177 lines (151 loc) · 6.56 KB
/
Oracle.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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.9;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { Governable } from "./legos/Governable.sol";
import { AggregatorV3Interface } from "./Interfaces.sol";
contract Oracle is Governable {
using SafeCast for uint256;
using SafeCast for int256;
mapping(address => address) public chainLinkAggregatorMap;
mapping(address => int256) public stablePrice;
uint256[50] private __gap;
function initialize(address _governance) external initializer {
_setGovernace(_governance);
}
function getUnderlyingPrice(address underlying)
virtual
external
view
returns(int256 answer)
{
if (stablePrice[underlying] != 0) {
return stablePrice[underlying];
}
(,answer,,,) = AggregatorV3Interface(chainLinkAggregatorMap[underlying]).latestRoundData();
require(answer > 0, "Oracle.getUnderlyingPrice.non_positive");
answer /= 100;
}
function getUnderlyingTwapPrice(address underlying, uint256 intervalInSeconds)
virtual
public
view
returns (int256)
{
if (stablePrice[underlying] != 0) {
return stablePrice[underlying];
}
AggregatorV3Interface aggregator = AggregatorV3Interface(chainLinkAggregatorMap[underlying]);
requireNonEmptyAddress(address(aggregator));
require(intervalInSeconds != 0, "interval can't be 0");
// 3 different timestamps, `previous`, `current`, `target`
// `base` = now - intervalInSeconds
// `current` = current round timestamp from aggregator
// `previous` = previous round timestamp from aggregator
// now >= previous > current > = < base
//
// while loop i = 0
// --+------+-----+-----+-----+-----+-----+
// base current now(previous)
//
// while loop i = 1
// --+------+-----+-----+-----+-----+-----+
// base current previous now
(uint80 round, uint256 latestPrice, uint256 latestTimestamp) = getLatestRoundData(aggregator);
uint256 baseTimestamp = _blockTimestamp() - intervalInSeconds;
// if latest updated timestamp is earlier than target timestamp, return the latest price.
if (latestTimestamp < baseTimestamp || round == 0) {
return formatPrice(latestPrice);
}
// rounds are like snapshots, latestRound means the latest price snapshot. follow chainlink naming
uint256 previousTimestamp = latestTimestamp;
uint256 cumulativeTime = _blockTimestamp() - previousTimestamp;
uint256 weightedPrice = latestPrice * cumulativeTime;
while (true) {
if (round == 0) {
// if cumulative time is less than requested interval, return current twap price
return formatPrice(weightedPrice / cumulativeTime);
}
round = round - 1; // check round sanity
(, uint256 currentPrice, uint256 currentTimestamp) = getRoundData(aggregator, round);
// check if current round timestamp is earlier than target timestamp
if (currentTimestamp <= baseTimestamp) {
// weighted time period will be (target timestamp - previous timestamp). For example,
// now is 1000, intervalInSeconds is 100, then target timestamp is 900. If timestamp of current round is 970,
// and timestamp of NEXT round is 880, then the weighted time period will be (970 - 900) = 70,
// instead of (970 - 880)
weightedPrice = weightedPrice + (currentPrice * (previousTimestamp - baseTimestamp));
break;
}
uint256 timeFraction = previousTimestamp - currentTimestamp;
weightedPrice = weightedPrice + (currentPrice * timeFraction);
cumulativeTime = cumulativeTime + timeFraction;
previousTimestamp = currentTimestamp;
}
return formatPrice(weightedPrice / intervalInSeconds);
}
//
// INTERNAL VIEW FUNCTIONS
//
function getLatestRoundData(AggregatorV3Interface _aggregator)
internal
view
returns (
uint80,
uint256 finalPrice,
uint256
)
{
(uint80 round, int256 latestPrice, , uint256 latestTimestamp, ) = _aggregator.latestRoundData();
finalPrice = uint256(latestPrice);
if (latestPrice <= 0) {
requireEnoughHistory(round);
(round, finalPrice, latestTimestamp) = getRoundData(_aggregator, round - 1);
}
return (round, finalPrice, latestTimestamp);
}
function getRoundData(AggregatorV3Interface _aggregator, uint80 _round)
internal
view
returns (
uint80,
uint256,
uint256
)
{
(uint80 round, int256 latestPrice, , uint256 latestTimestamp, ) = _aggregator.getRoundData(_round);
while (latestPrice <= 0) {
requireEnoughHistory(round);
round = round - 1;
(, latestPrice, , latestTimestamp, ) = _aggregator.getRoundData(round);
}
return (round, uint256(latestPrice), latestTimestamp);
}
function formatPrice(uint256 _price) internal pure returns (int256) {
return (_price / 100).toInt256(); // 6 decimals
}
function _blockTimestamp() internal view virtual returns (uint256) {
return block.timestamp;
}
// Internal
function requireEnoughHistory(uint80 _round) internal pure {
require(_round > 0, "Not enough history");
}
function requireNonEmptyAddress(address _addr) internal pure {
require(_addr != address(0), "empty address");
}
// Governance
function setAggregator(address underlying, address aggregator) external onlyGovernance {
requireNonEmptyAddress(underlying);
requireNonEmptyAddress(aggregator);
// oracle answer should be in 8 decimals
require(AggregatorV3Interface(aggregator).decimals() == 8, 'Expected oracle to have 8 decimals');
chainLinkAggregatorMap[underlying] = aggregator;
// AggregatorV3Interface(chainLinkAggregatorMap[underlying]).latestRoundData(); // sanity check
}
function setStablePrice(address underlying, int256 price) external onlyGovernance {
requireNonEmptyAddress(underlying);
require(price > 0, "stablePrice=0");
stablePrice[underlying] = price;
}
}