PythUtils.sol 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. // SPDX-License-Identifier: Apache-2.0
  2. pragma solidity ^0.8.0;
  3. import "./PythStructs.sol";
  4. library PythUtils {
  5. /// @notice Converts a Pyth price to a uint256 with a target number of decimals
  6. /// @param price The Pyth price
  7. /// @param expo The Pyth price exponent
  8. /// @param targetDecimals The target number of decimals
  9. /// @return The price as a uint256
  10. /// @dev Function will lose precision if targetDecimals is less than the Pyth price decimals.
  11. /// This method will truncate any digits that cannot be represented by the targetDecimals.
  12. /// e.g. If the price is 0.000123 and the targetDecimals is 2, the result will be 0
  13. function convertToUint(
  14. int64 price,
  15. int32 expo,
  16. uint8 targetDecimals
  17. ) public pure returns (uint256) {
  18. if (price < 0 || expo > 0 || expo < -255) {
  19. revert();
  20. }
  21. uint8 priceDecimals = uint8(uint32(-1 * expo));
  22. if (targetDecimals >= priceDecimals) {
  23. return
  24. uint(uint64(price)) *
  25. 10 ** uint32(targetDecimals - priceDecimals);
  26. } else {
  27. return
  28. uint(uint64(price)) /
  29. 10 ** uint32(priceDecimals - targetDecimals);
  30. }
  31. }
  32. /// @notice Calculates TWAP from two price points
  33. /// @dev The calculation is done by taking the difference of cumulative values and dividing by the time difference
  34. /// @param priceId The price feed ID
  35. /// @param twapPriceInfoStart The starting price point
  36. /// @param twapPriceInfoEnd The ending price point
  37. /// @return twapPriceFeed The calculated TWAP price feed
  38. function calculateTwap(
  39. bytes32 priceId,
  40. PythStructs.TwapPriceInfo memory twapPriceInfoStart,
  41. PythStructs.TwapPriceInfo memory twapPriceInfoEnd
  42. ) public pure returns (PythStructs.TwapPriceFeed memory twapPriceFeed) {
  43. twapPriceFeed.id = priceId;
  44. twapPriceFeed.startTime = twapPriceInfoStart.publishTime;
  45. twapPriceFeed.endTime = twapPriceInfoEnd.publishTime;
  46. // Calculate differences between start and end points for slots and cumulative values
  47. uint64 slotDiff = twapPriceInfoEnd.publishSlot -
  48. twapPriceInfoStart.publishSlot;
  49. int128 priceDiff = twapPriceInfoEnd.cumulativePrice -
  50. twapPriceInfoStart.cumulativePrice;
  51. uint128 confDiff = twapPriceInfoEnd.cumulativeConf -
  52. twapPriceInfoStart.cumulativeConf;
  53. // Calculate time-weighted average price (TWAP) and confidence by dividing
  54. // the difference in cumulative values by the number of slots between data points
  55. int128 twapPrice = priceDiff / int128(uint128(slotDiff));
  56. uint128 twapConf = confDiff / uint128(slotDiff);
  57. // The conversion from int128 to int64 is safe because:
  58. // 1. Individual prices fit within int64 by protocol design
  59. // 2. TWAP is essentially an average price over time (cumulativePrice₂-cumulativePrice₁)/slotDiff
  60. // 3. This average must be within the range of individual prices that went into the calculation
  61. // We use int128 only as an intermediate type to safely handle cumulative sums
  62. twapPriceFeed.twap.price = int64(twapPrice);
  63. twapPriceFeed.twap.conf = uint64(twapConf);
  64. twapPriceFeed.twap.expo = twapPriceInfoStart.expo;
  65. twapPriceFeed.twap.publishTime = twapPriceInfoEnd.publishTime;
  66. // Calculate downSlotsRatio as a value between 0 and 1,000,000
  67. // 0 means no slots were missed, 1,000,000 means all slots were missed
  68. uint64 totalDownSlots = twapPriceInfoEnd.numDownSlots -
  69. twapPriceInfoStart.numDownSlots;
  70. uint64 downSlotsRatio = (totalDownSlots * 1_000_000) / slotDiff;
  71. // Safely downcast to uint32 (sufficient for value range 0-1,000,000)
  72. twapPriceFeed.downSlotsRatio = uint32(downSlotsRatio);
  73. return twapPriceFeed;
  74. }
  75. }