Pyth.sol 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // contracts/Bridge.sol
  2. // SPDX-License-Identifier: Apache 2
  3. pragma solidity ^0.8.0;
  4. import "../libraries/external/BytesLib.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. contract Pyth is PythGetters, PythSetters, AbstractPyth {
  11. using BytesLib for bytes;
  12. function initialize(
  13. address wormhole,
  14. uint16 pyth2WormholeChainId,
  15. bytes32 pyth2WormholeEmitter
  16. ) virtual public {
  17. setWormhole(wormhole);
  18. setPyth2WormholeChainId(pyth2WormholeChainId);
  19. setPyth2WormholeEmitter(pyth2WormholeEmitter);
  20. }
  21. function updatePriceBatchFromVm(bytes memory encodedVm) public returns (PythInternalStructs.BatchPriceAttestation memory bpa) {
  22. (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
  23. require(valid, reason);
  24. require(verifyPythVM(vm), "invalid emitter");
  25. PythInternalStructs.BatchPriceAttestation memory batch = parseBatchPriceAttestation(vm.payload);
  26. for (uint i = 0; i < batch.attestations.length; i++) {
  27. PythInternalStructs.PriceAttestation memory attestation = batch.attestations[i];
  28. PythInternalStructs.PriceInfo memory latestPrice = latestPriceInfo(attestation.priceId);
  29. if(attestation.attestationTime > latestPrice.attestationTime) {
  30. setLatestPriceInfo(attestation.priceId, newPriceInfo(attestation));
  31. }
  32. }
  33. return batch;
  34. }
  35. function newPriceInfo(PythInternalStructs.PriceAttestation memory pa) private view returns (PythInternalStructs.PriceInfo memory info) {
  36. info.attestationTime = pa.attestationTime;
  37. info.publishTime = pa.publishTime;
  38. info.arrivalTime = block.timestamp;
  39. info.arrivalBlock = block.number;
  40. info.priceFeed.id = pa.priceId;
  41. info.priceFeed.price = pa.price;
  42. info.priceFeed.conf = pa.conf;
  43. info.priceFeed.expo = pa.expo;
  44. info.priceFeed.status = PythStructs.PriceStatus(pa.status);
  45. info.priceFeed.emaPrice = pa.emaPrice;
  46. info.priceFeed.emaConf = pa.emaConf;
  47. info.priceFeed.productId = pa.productId;
  48. info.priceFeed.numPublishers = pa.numPublishers;
  49. info.priceFeed.maxNumPublishers = pa.maxNumPublishers;
  50. return info;
  51. }
  52. function verifyPythVM(IWormhole.VM memory vm) private view returns (bool valid) {
  53. if (vm.emitterChainId != pyth2WormholeChainId()) {
  54. return false;
  55. }
  56. if (vm.emitterAddress != pyth2WormholeEmitter()) {
  57. return false;
  58. }
  59. return true;
  60. }
  61. function parseBatchPriceAttestation(bytes memory encoded) public pure returns (PythInternalStructs.BatchPriceAttestation memory bpa) {
  62. uint index = 0;
  63. // Check header
  64. bpa.header.magic = encoded.toUint32(index);
  65. index += 4;
  66. require(bpa.header.magic == 0x50325748, "invalid magic value");
  67. bpa.header.versionMajor = encoded.toUint16(index);
  68. index += 2;
  69. require(bpa.header.versionMajor == 3, "invalid version major, expected 3");
  70. bpa.header.versionMinor = encoded.toUint16(index);
  71. index += 2;
  72. require(bpa.header.versionMinor >= 0, "invalid version minor, expected 0 or more");
  73. bpa.header.hdrSize = encoded.toUint16(index);
  74. index += 2;
  75. // NOTE(2022-04-19): Currently, only payloadId comes after
  76. // hdrSize. Future extra header fields must be read using a
  77. // separate offset to respect hdrSize, i.e.:
  78. //
  79. // uint hdrIndex = 0;
  80. // bpa.header.payloadId = encoded.toUint8(index + hdrIndex);
  81. // hdrIndex += 1;
  82. //
  83. // bpa.header.someNewField = encoded.toUint32(index + hdrIndex);
  84. // hdrIndex += 4;
  85. //
  86. // // Skip remaining unknown header bytes
  87. // index += bpa.header.hdrSize;
  88. bpa.header.payloadId = encoded.toUint8(index);
  89. // Skip remaining unknown header bytes
  90. index += bpa.header.hdrSize;
  91. // Payload ID of 2 required for batch headerBa
  92. require(bpa.header.payloadId == 2, "invalid payload ID, expected 2 for BatchPriceAttestation");
  93. // Parse the number of attestations
  94. bpa.nAttestations = encoded.toUint16(index);
  95. index += 2;
  96. // Parse the attestation size
  97. bpa.attestationSize = encoded.toUint16(index);
  98. index += 2;
  99. require(encoded.length == (index + (bpa.attestationSize * bpa.nAttestations)), "invalid BatchPriceAttestation size");
  100. bpa.attestations = new PythInternalStructs.PriceAttestation[](bpa.nAttestations);
  101. // Deserialize each attestation
  102. for (uint j=0; j < bpa.nAttestations; j++) {
  103. // NOTE: We don't advance the global index immediately.
  104. // attestationIndex is an attestation-local offset used
  105. // for readability and easier debugging.
  106. uint attestationIndex = 0;
  107. // Attestation
  108. bpa.attestations[j].productId = encoded.toBytes32(index + attestationIndex);
  109. attestationIndex += 32;
  110. bpa.attestations[j].priceId = encoded.toBytes32(index + attestationIndex);
  111. attestationIndex += 32;
  112. bpa.attestations[j].price = int64(encoded.toUint64(index + attestationIndex));
  113. attestationIndex += 8;
  114. bpa.attestations[j].conf = encoded.toUint64(index + attestationIndex);
  115. attestationIndex += 8;
  116. bpa.attestations[j].expo = int32(encoded.toUint32(index + attestationIndex));
  117. attestationIndex += 4;
  118. bpa.attestations[j].emaPrice = int64(encoded.toUint64(index + attestationIndex));
  119. attestationIndex += 8;
  120. bpa.attestations[j].emaConf = encoded.toUint64(index + attestationIndex);
  121. attestationIndex += 8;
  122. bpa.attestations[j].status = encoded.toUint8(index + attestationIndex);
  123. attestationIndex += 1;
  124. bpa.attestations[j].numPublishers = encoded.toUint32(index + attestationIndex);
  125. attestationIndex += 4;
  126. bpa.attestations[j].maxNumPublishers = encoded.toUint32(index + attestationIndex);
  127. attestationIndex += 4;
  128. bpa.attestations[j].attestationTime = encoded.toUint64(index + attestationIndex);
  129. attestationIndex += 8;
  130. bpa.attestations[j].publishTime = encoded.toUint64(index + attestationIndex);
  131. attestationIndex += 8;
  132. bpa.attestations[j].prevPublishTime = encoded.toUint64(index + attestationIndex);
  133. attestationIndex += 8;
  134. bpa.attestations[j].prevPrice = int64(encoded.toUint64(index + attestationIndex));
  135. attestationIndex += 8;
  136. bpa.attestations[j].prevConf = encoded.toUint64(index + attestationIndex);
  137. attestationIndex += 8;
  138. require(attestationIndex <= bpa.attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes");
  139. // Respect specified attestation size for forward-compat
  140. index += bpa.attestationSize;
  141. }
  142. }
  143. /// Maximum acceptable time period before price is considered to be stale.
  144. ///
  145. /// This includes attestation delay which currently might up to a minute.
  146. uint private constant VALID_TIME_PERIOD_SECS = 180;
  147. function queryPriceFeed(bytes32 id) public view override returns (PythStructs.PriceFeed memory priceFeed){
  148. // Look up the latest price info for the given ID
  149. PythInternalStructs.PriceInfo memory info = latestPriceInfo(id);
  150. require(info.priceFeed.id != 0, "no price feed found for the given price id");
  151. // Check that there is not a significant difference between this chain's time
  152. // and the attestation time. This is a last-resort safety net, and this check
  153. // will be iterated on in the future.
  154. if (diff(block.timestamp, info.attestationTime) > VALID_TIME_PERIOD_SECS) {
  155. info.priceFeed.status = PythStructs.PriceStatus.UNKNOWN;
  156. }
  157. return info.priceFeed;
  158. }
  159. function diff(uint x, uint y) private pure returns (uint) {
  160. if (x > y) {
  161. return x - y;
  162. } else {
  163. return y - x;
  164. }
  165. }
  166. }