-
Notifications
You must be signed in to change notification settings - Fork 0
/
PriceOracle.sol
223 lines (203 loc) · 8.14 KB
/
PriceOracle.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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.6;
import '@chainlink/contracts/src/v0.7/interfaces/AggregatorV3Interface.sol';
import '@uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol';
import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import './interfaces/IPriceOracle.sol';
contract PriceOracle is Initializable, OwnableUpgradeable, IPriceOracle {
using SafeMath for uint256;
uint32 uniswapPriceAveragingPeriod;
struct PriceData {
address oracle;
uint256 decimals;
}
/**
* @notice stores the price oracle and its decimals for chainlink feeds
**/
mapping(address => PriceData) public chainlinkFeedAddresses;
mapping(address => uint256) decimals;
/**
* @notice stores the addresses of price feeds for uniswap token pairs
**/
mapping(bytes32 => address) public uniswapPools;
/**
* @notice Used to initialize the price oracle contract
* @dev can only be invoked once
* @param _admin owner of the price oracle
**/
function initialize(address _admin) external initializer {
OwnableUpgradeable.__Ownable_init();
OwnableUpgradeable.transferOwnership(_admin);
}
/**
* @notice Used to get price of the num vs den token from chainlink
* @param num the address of the token for which price in queried
* @param den the address of the token in which price is queried
* @return price of the num in terms of den
* @return no of decimals for the price
**/
function getChainlinkLatestPrice(address num, address den) public view returns (uint256, uint256) {
PriceData memory _feedData1 = chainlinkFeedAddresses[num];
PriceData memory _feedData2 = chainlinkFeedAddresses[den];
if (_feedData1.oracle == address(0) || _feedData2.oracle == address(0)) {
return (0, 0);
}
int256 price1;
int256 price2;
{
uint80 roundID1;
uint256 timeStamp1;
uint80 answeredInRound1;
(
roundID1,
price1,
,
timeStamp1,
answeredInRound1
) = AggregatorV3Interface(_feedData1.oracle).latestRoundData();
if(timeStamp1 == 0 || answeredInRound1 < roundID1) {
return (0, 0);
}
}
{
uint80 roundID2;
uint256 timeStamp2;
uint80 answeredInRound2;
(
roundID2,
price2,
,
timeStamp2,
answeredInRound2
) = AggregatorV3Interface(_feedData2.oracle).latestRoundData();
if(timeStamp2 == 0 || answeredInRound2 < roundID2) {
return (0, 0);
}
}
uint256 price = uint256(price1)
.mul(10**_feedData2.decimals)
.mul(10**30)
.div(uint256(price2))
.div(10**_feedData1.decimals)
.mul(10**decimals[den])
.div(10**decimals[num]);
return (price, 30);
}
/**
* @notice Used to get decimals for a token
* @param _token address of the token
* @return number of decimals for the token
**/
function getDecimals(address _token) internal view returns (uint8) {
if (_token == address(0)) {
return 18;
}
try ERC20(_token).decimals() returns (uint8 v) {
return v;
} catch Error(string memory) {
return 0;
} catch (bytes memory) {
return 0;
}
}
/**
* @notice Used to get price of the num vs den token from uniswap
* @param num the address of the token for which price in queried
* @param den the address of the token in which price is queried
* @return price of the num in terms of den
* @return no of decimals for the price
**/
function getUniswapLatestPrice(address num, address den) public view returns (uint256, uint256) {
bytes32 _poolTokensId = getUniswapPoolTokenId(num, den);
address _pool = uniswapPools[_poolTokensId];
if (_pool == address(0)) {
return (0, 0);
}
int24 _twapTick = OracleLibrary.consult(_pool, uniswapPriceAveragingPeriod);
uint256 _numTokens = OracleLibrary.getQuoteAtTick(_twapTick, 10**30, num, den);
return (_numTokens, 30);
}
function getUniswapPoolTokenId(address num, address den) internal pure returns (bytes32) {
if (uint256(num) < uint256(den)) {
return keccak256(abi.encodePacked(num, den));
} else {
return keccak256(abi.encodePacked(den, num));
}
}
/**
* @notice Used to get price of the num vs den token
* @param num the address of the token for which price in queried
* @param den the address of the token in which price is queried
* @return price of the num in terms of den
* @return no of decimals for the price
**/
function getLatestPrice(address num, address den) external view override returns (uint256, uint256) {
uint256 _price;
uint256 _decimals;
(_price, _decimals) = getChainlinkLatestPrice(num, den);
if (_decimals != 0) {
return (_price, _decimals);
}
(_price, _decimals) = getUniswapLatestPrice(num, den);
if (_decimals != 0) {
return (_price, _decimals);
}
revert("PriceOracle::getLatestPrice - Price Feed doesn't exist");
}
/**
* @notice used to check if price feed exists between 2 tokens
* @param token1 one of the token for which price feed is to be checked
* @param token2 other token for which price feed is to be checked
* @return if price feed exists for the token pair
**/
function doesFeedExist(address token1, address token2) external view override returns (bool) {
if (chainlinkFeedAddresses[token1].oracle != address(0) && chainlinkFeedAddresses[token2].oracle != address(0)) {
return true;
}
bytes32 _poolTokensId = getUniswapPoolTokenId(token1, token2);
if (uniswapPools[_poolTokensId] != address(0)) {
return true;
}
return false;
}
/**
* @notice Used to set the price feed address for a token in chainlink
* @dev only owner can set
* @param token address of token for which price feed is added
* @param priceOracle addrewss of the price feed for the token
**/
function setChainlinkFeedAddress(address token, address priceOracle) external onlyOwner {
uint256 priceOracleDecimals = AggregatorV3Interface(priceOracle).decimals();
chainlinkFeedAddresses[token] = PriceData(priceOracle, priceOracleDecimals);
decimals[token] = getDecimals(token);
emit ChainlinkFeedUpdated(token, priceOracle);
}
/**
* @notice Used to set the price feed address for a token pair in uniswap
* @dev only owner can set
* @param token1 address of one of the tokens for which price feed is added
* @param token2 address of other token for which price feed is added
* @param pool addrewss of the price feed for the token pair
**/
function setUniswapFeedAddress(
address token1,
address token2,
address pool
) external onlyOwner {
require(token1 != token2, 'token1 and token2 should be different addresses');
bytes32 _poolTokensId = getUniswapPoolTokenId(token1, token2);
uniswapPools[_poolTokensId] = pool;
emit UniswapFeedUpdated(token1, token2, _poolTokensId, pool);
}
/**
* @notice Used to set the period in which uniswap price is averaged
* @dev only owner can set. This is used to prevent attacks to control price feed
* @param _uniswapPriceAveragingPeriod period for uniswap price averaging
**/
function setUniswapPriceAveragingPeriod(uint32 _uniswapPriceAveragingPeriod) external onlyOwner {
uniswapPriceAveragingPeriod = _uniswapPriceAveragingPeriod;
emit UniswapPriceAveragingPeriodUpdated(_uniswapPriceAveragingPeriod);
}
}