PythUtils.sol 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. // SPDX-License-Identifier: Apache-2.0
  2. pragma solidity ^0.8.0;
  3. import "./PythStructs.sol";
  4. import "./PythErrors.sol";
  5. import "./Math.sol";
  6. library PythUtils {
  7. uint8 public constant PRECISION = 36;
  8. /// @notice Converts a Pyth price to a uint256 with a target number of decimals
  9. /// @param price The Pyth price
  10. /// @param expo The Pyth price exponent
  11. /// @param targetDecimals The target number of decimals
  12. /// @return The price as a uint256
  13. /// @dev Function will lose precision if targetDecimals is less than the Pyth price decimals.
  14. /// This method will truncate any digits that cannot be represented by the targetDecimals.
  15. /// e.g. If the price is 0.000123 and the targetDecimals is 2, the result will be 0
  16. /// This function will revert with PythErrors.ExponentOverflow if the combined exponent (targetDecimals + expo) is greater than 58 or less than -58.
  17. /// Assuming the combined exponent is within bounds, this function will work for full range of int64 prices.
  18. /// The result of the computation is rounded down. In particular, if the result is < 1 in the delta exponent, it will be rounded to 0
  19. function convertToUint(
  20. int64 price,
  21. int32 expo,
  22. uint8 targetDecimals
  23. ) public pure returns (uint256) {
  24. if (price < 0) {
  25. revert PythErrors.NegativeInputPrice();
  26. }
  27. if (expo < -255) {
  28. revert PythErrors.InvalidInputExpo();
  29. }
  30. // If targetDecimals is 6, we want to multiply the final price by 10 ** -6
  31. // So the delta exponent is targetDecimals + currentExpo
  32. int32 deltaExponent = int32(uint32(targetDecimals)) + expo;
  33. // Bounds check: prevent overflow/underflow with base 10 exponentiation
  34. // Calculation: 10 ** n <= (2 ** 256 - 63) - 1
  35. // n <= log10((2 ** 193) - 1)
  36. // n <= 58.2
  37. if (deltaExponent > 58 || deltaExponent < -58)
  38. revert PythErrors.ExponentOverflow();
  39. // We can safely cast the price to uint256 because the above condition will revert if the price is negative
  40. uint256 unsignedPrice = uint256(uint64(price));
  41. if (deltaExponent > 0) {
  42. (bool success, uint256 result) = Math.tryMul(
  43. unsignedPrice,
  44. 10 ** uint32(deltaExponent)
  45. );
  46. // This condition is unreachable since we validated deltaExponent bounds above.
  47. // But keeping it here for safety.
  48. if (!success) {
  49. revert PythErrors.CombinedPriceOverflow();
  50. }
  51. return result;
  52. } else {
  53. (bool success, uint256 result) = Math.tryDiv(
  54. unsignedPrice,
  55. 10 ** uint(Math.abs(deltaExponent))
  56. );
  57. // This condition is unreachable since we validated deltaExponent bounds above.
  58. // But keeping it here for safety.
  59. if (!success) {
  60. revert PythErrors.CombinedPriceOverflow();
  61. }
  62. return result;
  63. }
  64. }
  65. /// @notice Combines two prices to get a cross-rate
  66. /// @param price1 The first price (a/b)
  67. /// @param expo1 The exponent of the first price
  68. /// @param price2 The second price (c/b)
  69. /// @param expo2 The exponent of the second price
  70. /// @param targetExponent The target exponent for the cross-rate
  71. /// @return crossRate The cross-rate (a/c)
  72. /// @dev This function will revert if either price is negative or if the exponents are less than -255.
  73. /// @notice This function doesn't return the combined confidence interval.
  74. /// This function will revert with PythErrors.ExponentOverflow if the combined exponent (targetExponent + expo1 - expo2) is greater than 58 or less than -58.
  75. /// Assuming the combined exponent is within bounds, this function will work for full range of int64 prices.
  76. /// The result of the computation is rounded down. In particular, if the result is < 1 in the delta exponent, it will be rounded to 0
  77. function deriveCrossRate(
  78. int64 price1,
  79. int32 expo1,
  80. int64 price2,
  81. int32 expo2,
  82. int32 targetExponent
  83. ) public pure returns (uint256 crossRate) {
  84. // Check if the input prices are negative
  85. if (price1 < 0 || price2 < 0) {
  86. revert PythErrors.NegativeInputPrice();
  87. }
  88. // Check if the input exponents are valid and not less than -255
  89. if (expo1 < -255 || expo2 < -255 || targetExponent < -255) {
  90. revert PythErrors.InvalidInputExpo();
  91. }
  92. // note: This value can be negative.
  93. int64 deltaExponent = int64(expo1 - (expo2 + targetExponent));
  94. // Bounds check: prevent overflow/underflow with base 10 exponentiation
  95. // Calculation: 10 ** n <= (2 ** 256 - 63) - 1
  96. // n <= log10((2 ** 193) - 1)
  97. // n <= 58.2
  98. if (deltaExponent > 58 || deltaExponent < -58)
  99. revert PythErrors.ExponentOverflow();
  100. uint256 result;
  101. if (deltaExponent > 0) {
  102. result = Math.mulDiv(
  103. uint64(price1),
  104. 10 ** uint64(deltaExponent),
  105. uint64(price2)
  106. );
  107. } else {
  108. result = Math.mulDiv(
  109. uint64(price1),
  110. 1,
  111. 10 ** uint64(Math.abs(deltaExponent)) * uint64(price2)
  112. );
  113. }
  114. return result;
  115. }
  116. }