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