DEXFeed.sol
1 pragma solidity >=0.6.0; 2 pragma experimental ABIEncoderV2; 3 4 import "../interfaces/AggregatorV3Interface.sol"; 5 import "../interfaces/TimeProvider.sol"; 6 import "../interfaces/UnderlyingFeed.sol"; 7 import "../utils/MoreMath.sol"; 8 import "../utils/SafeCast.sol"; 9 import "../utils/SafeMath.sol"; 10 import "../utils/SignedSafeMath.sol"; 11 12 contract DEXFeed is UnderlyingFeed { 13 14 using SafeCast for int; 15 using SafeCast for uint; 16 using SafeMath for uint; 17 using SignedSafeMath for int; 18 19 struct Sample { 20 uint32 timestamp; 21 int128 price; 22 } 23 24 AggregatorV3Interface private aggregator; 25 TimeProvider private time; 26 27 mapping(uint => Sample) private dailyPrices; 28 mapping(uint => mapping(uint => uint)) private dailyVolatilities; 29 30 string private _symbol; 31 address private udlAddr; 32 Sample[] private samples; 33 uint private offset; 34 int private priceN; 35 int private priceD; 36 37 constructor( 38 string memory _sb, 39 address _udlAddr, 40 address _aggregator, 41 address _time, 42 uint _offset, 43 uint[] memory _timestamps, 44 int[] memory _prices 45 ) 46 public 47 { 48 _symbol = _sb; 49 aggregator = AggregatorV3Interface(_aggregator); 50 time = TimeProvider(_time); 51 udlAddr = _udlAddr; 52 offset = _offset; 53 initialize(_timestamps, _prices); 54 } 55 56 function initialize(uint[] memory _timestamps, int[] memory _prices) public { 57 58 require(samples.length == 0, "already initialized"); 59 60 initializeDecimals(); 61 initializeSamples(_timestamps, _prices); 62 } 63 64 function symbol() override external view returns (string memory) { 65 66 return _symbol; 67 } 68 69 function getUnderlyingAddr() override external view returns (address) { 70 71 return udlAddr; 72 } 73 74 function getPrivledgedPublisherKeeper() override external view returns (address) { 75 return address(0); 76 } 77 78 function getUnderlyingAggAddr() override external view returns (address) { 79 return address(aggregator); 80 } 81 82 function getLatestPrice() override external view returns (uint timestamp, int price) { 83 84 (, price,, timestamp,) = aggregator.latestRoundData(); 85 price = int(rescalePrice(price)); 86 } 87 88 function getPrice(uint position) 89 override 90 external 91 view 92 returns (uint timestamp, int price) 93 { 94 (timestamp, price,) = getPriceCached(position); 95 } 96 97 function getPriceCached(uint position) 98 public 99 view 100 returns (uint timestamp, int price, bool cached) 101 { 102 if ((position.mod(1 days) == 0) && (dailyPrices[position].timestamp != 0)) { 103 104 timestamp = position; 105 price = dailyPrices[position].price; 106 cached = true; 107 108 } else { 109 110 uint len = samples.length; 111 112 require(len > 0, "no sample"); 113 114 require( 115 samples[0].timestamp <= position && samples[len - 1].timestamp >= position, 116 string(abi.encodePacked("invalid position (prefetch needed): ", MoreMath.toString(position))) 117 ); 118 119 uint start = 0; 120 uint end = len - 1; 121 122 while (true) { 123 uint m = (start.add(end).add(1)).div(2); 124 Sample memory s = samples[m]; 125 126 if ((s.timestamp == position) || (end == m)) { 127 if (samples[start].timestamp == position) { 128 s = samples[start]; 129 } 130 timestamp = s.timestamp; 131 price = s.price; 132 break; 133 } 134 135 if (s.timestamp > position) 136 end = m; 137 else 138 start = m; 139 } 140 } 141 } 142 143 function getDailyVolatility(uint timespan) override external view returns (uint vol) { 144 145 (vol, ) = getDailyVolatilityCached(timespan); 146 } 147 148 function getDailyVolatilityCached(uint timespan) override public view returns (uint vol, bool cached) { 149 150 uint period = timespan.div(1 days); 151 timespan = period.mul(1 days); 152 int[] memory array = new int[](period - 1); 153 154 if (dailyVolatilities[timespan][today()] == 0) { 155 156 int prev; 157 int pBase = 1e9; 158 159 for (uint i = 0; i < period; i++) { 160 uint position = today().sub(timespan).add(i.add(1).mul(1 days)); 161 (, int price,) = getPriceCached(position); 162 if (i > 0) { 163 array[i.sub(1)] = price.mul(pBase).div(prev); 164 } 165 prev = price; 166 } 167 168 vol = MoreMath.std(array).mul(uint(prev)).div(uint(pBase)); 169 170 } else { 171 172 vol = decodeValue(dailyVolatilities[timespan][today()]); 173 cached = true; 174 175 } 176 } 177 178 function calcLowerVolatility(uint vol) override external view returns (uint lowerVol) { 179 180 lowerVol = vol.mul(3).div(2); 181 } 182 183 function calcUpperVolatility(uint vol) override external view returns (uint upperVol) { 184 185 upperVol = vol.mul(3); 186 } 187 188 function prefetchSample() override external { 189 190 (, int price,, uint timestamp,) = aggregator.latestRoundData(); 191 price = rescalePrice(price); 192 require(timestamp > samples[samples.length - 1].timestamp, "already up to date"); 193 samples.push(Sample(timestamp.toUint32(), price.toInt128())); 194 } 195 196 function prefetchDailyPrice(uint roundId) override external { 197 198 int price; 199 uint timestamp; 200 201 if (roundId == 0) { 202 (, price,, timestamp,) = aggregator.latestRoundData(); 203 } else { 204 (, price,, timestamp,) = aggregator.getRoundData(uint80(roundId)); 205 } 206 price = rescalePrice(price); 207 208 uint key = timestamp.div(1 days).mul(1 days); 209 Sample memory s = Sample(timestamp.toUint32(), price.toInt128()); 210 211 require( 212 dailyPrices[key].timestamp == 0 || dailyPrices[key].timestamp > s.timestamp, 213 "price already set" 214 ); 215 dailyPrices[key] = s; 216 217 if (samples.length == 0 || samples[samples.length - 1].timestamp < s.timestamp) { 218 samples.push(s); 219 } 220 } 221 222 function prefetchDailyVolatility(uint timespan) override external { 223 224 require(timespan.mod(1 days) == 0, "invalid timespan"); 225 226 if (dailyVolatilities[timespan][today()] == 0) { 227 (uint vol, bool cached) = getDailyVolatilityCached(timespan); 228 require(!cached, "already cached"); 229 dailyVolatilities[timespan][today()] = encodeValue(vol); 230 } 231 } 232 233 function initializeDecimals() private { 234 235 int exchangeDecimals = 18; 236 int diff = exchangeDecimals.sub(int(aggregator.decimals())); 237 238 require(-18 <= diff && diff <= 18, "invalid decimals"); 239 240 if (diff > 0) { 241 priceN = int(10 ** uint(diff)); 242 priceD = 1; 243 } else { 244 priceN = 1; 245 priceD = int(10 ** uint(-diff)); 246 } 247 } 248 249 function initializeSamples(uint[] memory _timestamps, int[] memory _prices) private { 250 251 for (uint i = 0; i < _timestamps.length; i++) { 252 253 uint ts = _timestamps[i]; 254 int pc = _prices[i]; 255 Sample memory s = Sample(ts.toUint32(), rescalePrice(pc)); 256 257 if (ts.mod(1 days) == 0) { 258 dailyPrices[ts] = s; 259 } 260 261 samples.push(s); 262 } 263 } 264 265 function rescalePrice(int price) private view returns (int128) { 266 267 return price.mul(priceN).div(priceD).toInt128(); 268 } 269 270 function encodeValue(uint v) private pure returns (uint) { 271 return v | (uint(1) << 255); 272 } 273 274 function decodeValue(uint v) private pure returns (uint) { 275 return v & (~(uint(1) << 255)); 276 } 277 278 function today() private view returns(uint) { 279 280 return time.getNow().sub(offset).div(1 days).mul(1 days); 281 } 282 }