/ contracts / feeds / ChainlinkFeed.sol
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  }