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