Pyth.sol 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. // contracts/Bridge.sol
  2. // SPDX-License-Identifier: Apache 2
  3. pragma solidity ^0.8.0;
  4. import "../libraries/external/UnsafeBytesLib.sol";
  5. import "@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol";
  6. import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
  7. import "./PythGetters.sol";
  8. import "./PythSetters.sol";
  9. import "./PythInternalStructs.sol";
  10. abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
  11. function _initialize(
  12. address wormhole,
  13. uint16 pyth2WormholeChainId,
  14. bytes32 pyth2WormholeEmitter
  15. ) internal {
  16. setWormhole(wormhole);
  17. setPyth2WormholeChainId(pyth2WormholeChainId);
  18. setPyth2WormholeEmitter(pyth2WormholeEmitter);
  19. }
  20. function updatePriceBatchFromVm(bytes calldata encodedVm) private {
  21. (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
  22. require(valid, reason);
  23. require(verifyPythVM(vm), "invalid data source chain/emitter ID");
  24. parseAndProcessBatchPriceAttestation(vm);
  25. }
  26. function updatePriceFeeds(bytes[] calldata updateData) public override payable {
  27. uint requiredFee = getUpdateFee(updateData);
  28. require(msg.value >= requiredFee, "insufficient paid fee amount");
  29. for(uint i = 0; i < updateData.length; ) {
  30. updatePriceBatchFromVm(updateData[i]);
  31. unchecked { i++; }
  32. }
  33. emit UpdatePriceFeeds(msg.sender, updateData.length, requiredFee);
  34. }
  35. /// This method is deprecated, please use the `getUpdateFee(bytes[])` instead.
  36. function getUpdateFee(uint updateDataSize) public view returns (uint feeAmount) {
  37. return singleUpdateFeeInWei() * updateDataSize;
  38. }
  39. function getUpdateFee(bytes[] calldata updateData) public override view returns (uint feeAmount) {
  40. return singleUpdateFeeInWei() * updateData.length;
  41. }
  42. function verifyPythVM(IWormhole.VM memory vm) private view returns (bool valid) {
  43. return isValidDataSource(vm.emitterChainId, vm.emitterAddress);
  44. }
  45. function parseAndProcessBatchPriceAttestation(IWormhole.VM memory vm) internal {
  46. // Most of the math operations below are simple additions.
  47. // In the places that there is more complex operation there is
  48. // a comment explaining why it is safe. Also, byteslib
  49. // operations have proper require.
  50. unchecked {
  51. bytes memory encoded = vm.payload;
  52. uint index = 0;
  53. // Check header
  54. {
  55. uint32 magic = UnsafeBytesLib.toUint32(encoded, index);
  56. index += 4;
  57. require(magic == 0x50325748, "invalid magic value");
  58. uint16 versionMajor = UnsafeBytesLib.toUint16(encoded, index);
  59. index += 2;
  60. require(versionMajor == 3, "invalid version major, expected 3");
  61. uint16 versionMinor = UnsafeBytesLib.toUint16(encoded, index);
  62. index += 2;
  63. require(versionMinor >= 0, "invalid version minor, expected 0 or more");
  64. uint16 hdrSize = UnsafeBytesLib.toUint16(encoded, index);
  65. index += 2;
  66. // NOTE(2022-04-19): Currently, only payloadId comes after
  67. // hdrSize. Future extra header fields must be read using a
  68. // separate offset to respect hdrSize, i.e.:
  69. //
  70. // uint hdrIndex = 0;
  71. // bpa.header.payloadId = UnsafeBytesLib.toUint8(encoded, index + hdrIndex);
  72. // hdrIndex += 1;
  73. //
  74. // bpa.header.someNewField = UnsafeBytesLib.toUint32(encoded, index + hdrIndex);
  75. // hdrIndex += 4;
  76. //
  77. // // Skip remaining unknown header bytes
  78. // index += bpa.header.hdrSize;
  79. uint8 payloadId = UnsafeBytesLib.toUint8(encoded, index);
  80. // Skip remaining unknown header bytes
  81. index += hdrSize;
  82. // Payload ID of 2 required for batch headerBa
  83. require(payloadId == 2, "invalid payload ID, expected 2 for BatchPriceAttestation");
  84. }
  85. // Parse the number of attestations
  86. uint16 nAttestations = UnsafeBytesLib.toUint16(encoded, index);
  87. index += 2;
  88. // Parse the attestation size
  89. uint16 attestationSize = UnsafeBytesLib.toUint16(encoded, index);
  90. index += 2;
  91. // Given the message is valid the arithmetic below should not overflow, and
  92. // even if it overflows then the require would fail.
  93. require(encoded.length == (index + (attestationSize * nAttestations)), "invalid BatchPriceAttestation size");
  94. PythInternalStructs.PriceInfo memory info;
  95. bytes32 priceId;
  96. uint freshPrices = 0;
  97. // Deserialize each attestation
  98. for (uint j=0; j < nAttestations; j++) {
  99. // NOTE: We don't advance the global index immediately.
  100. // attestationIndex is an attestation-local offset used
  101. // for readability and easier debugging.
  102. uint attestationIndex = 0;
  103. // Unused bytes32 product id
  104. attestationIndex += 32;
  105. priceId = UnsafeBytesLib.toBytes32(encoded, index + attestationIndex);
  106. attestationIndex += 32;
  107. info.price = int64(UnsafeBytesLib.toUint64(encoded, index + attestationIndex));
  108. attestationIndex += 8;
  109. info.conf = UnsafeBytesLib.toUint64(encoded, index + attestationIndex);
  110. attestationIndex += 8;
  111. info.expo = int32(UnsafeBytesLib.toUint32(encoded, index + attestationIndex));
  112. attestationIndex += 4;
  113. info.emaPrice = int64(UnsafeBytesLib.toUint64(encoded, index + attestationIndex));
  114. attestationIndex += 8;
  115. info.emaConf = UnsafeBytesLib.toUint64(encoded, index + attestationIndex);
  116. attestationIndex += 8;
  117. {
  118. // Status is an enum (encoded as uint8) with the following values:
  119. // 0 = UNKNOWN: The price feed is not currently updating for an unknown reason.
  120. // 1 = TRADING: The price feed is updating as expected.
  121. // 2 = HALTED: The price feed is not currently updating because trading in the product has been halted.
  122. // 3 = AUCTION: The price feed is not currently updating because an auction is setting the price.
  123. uint8 status = UnsafeBytesLib.toUint8(encoded, index + attestationIndex);
  124. attestationIndex += 1;
  125. // Unused uint32 numPublishers
  126. attestationIndex += 4;
  127. // Unused uint32 numPublishers
  128. attestationIndex += 4;
  129. // Unused uint64 attestationTime
  130. attestationIndex += 8;
  131. info.publishTime = UnsafeBytesLib.toUint64(encoded, index + attestationIndex);
  132. attestationIndex += 8;
  133. if (status == 1) { // status == TRADING
  134. attestationIndex += 24;
  135. } else {
  136. // If status is not trading then the latest available price is
  137. // the previous price info that are passed here.
  138. // Previous publish time
  139. info.publishTime = UnsafeBytesLib.toUint64(encoded, index + attestationIndex);
  140. attestationIndex += 8;
  141. // Previous price
  142. info.price = int64(UnsafeBytesLib.toUint64(encoded, index + attestationIndex));
  143. attestationIndex += 8;
  144. // Previous confidence
  145. info.conf = UnsafeBytesLib.toUint64(encoded, index + attestationIndex);
  146. attestationIndex += 8;
  147. }
  148. }
  149. require(attestationIndex <= attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes");
  150. // Respect specified attestation size for forward-compat
  151. index += attestationSize;
  152. // Store the attestation
  153. uint64 latestPublishTime = latestPriceInfoPublishTime(priceId);
  154. bool fresh = false;
  155. if(info.publishTime > latestPublishTime) {
  156. freshPrices += 1;
  157. fresh = true;
  158. setLatestPriceInfo(priceId, info);
  159. }
  160. emit PriceFeedUpdate(priceId, fresh, vm.emitterChainId, vm.sequence, latestPublishTime,
  161. info.publishTime, info.price, info.conf);
  162. }
  163. emit BatchPriceFeedUpdate(vm.emitterChainId, vm.sequence, nAttestations, freshPrices);
  164. }
  165. }
  166. function queryPriceFeed(bytes32 id) public view override returns (PythStructs.PriceFeed memory priceFeed){
  167. // Look up the latest price info for the given ID
  168. PythInternalStructs.PriceInfo memory info = latestPriceInfo(id);
  169. require(info.publishTime != 0, "price feed for the given id is not pushed or does not exist");
  170. priceFeed.id = id;
  171. priceFeed.price.price = info.price;
  172. priceFeed.price.conf = info.conf;
  173. priceFeed.price.expo = info.expo;
  174. priceFeed.price.publishTime = uint(info.publishTime);
  175. priceFeed.emaPrice.price = info.emaPrice;
  176. priceFeed.emaPrice.conf = info.emaConf;
  177. priceFeed.emaPrice.expo = info.expo;
  178. priceFeed.emaPrice.publishTime = uint(info.publishTime);
  179. }
  180. function priceFeedExists(bytes32 id) public override view returns (bool) {
  181. return (latestPriceInfoPublishTime(id) != 0);
  182. }
  183. function getValidTimePeriod() public override view returns (uint) {
  184. return validTimePeriodSeconds();
  185. }
  186. function version() public pure returns (string memory) {
  187. return "1.1.0";
  188. }
  189. }