Pyth.sol 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // contracts/Bridge.sol
  2. // SPDX-License-Identifier: Apache 2
  3. pragma solidity ^0.8.0;
  4. import "../libraries/external/BytesLib.sol";
  5. import "./PythGetters.sol";
  6. import "./PythSetters.sol";
  7. import "./PythStructs.sol";
  8. import "./PythGovernance.sol";
  9. contract Pyth is PythGovernance {
  10. using BytesLib for bytes;
  11. function attestPrice(bytes memory encodedVm) public returns (PythStructs.PriceAttestation memory pa) {
  12. (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
  13. require(valid, reason);
  14. require(verifyPythVM(vm), "invalid emitter");
  15. PythStructs.PriceAttestation memory price = parsePriceAttestation(vm.payload);
  16. PythStructs.PriceInfo memory latestPrice = latestPriceInfo(pa.priceId);
  17. if(price.timestamp > latestPrice.attestation_time) {
  18. setLatestPriceInfo(price.priceId, newPriceInfo(price));
  19. }
  20. return price;
  21. }
  22. function newPriceInfo(PythStructs.PriceAttestation memory pa) private view returns (PythStructs.PriceInfo memory info) {
  23. info.attestation_time = pa.timestamp;
  24. info.arrival_time = block.timestamp;
  25. info.arrival_block = block.number;
  26. info.price.id = pa.priceId;
  27. info.price.price = pa.price;
  28. info.price.conf = pa.confidenceInterval;
  29. info.price.status = PythSDK.PriceStatus(pa.status);
  30. info.price.expo = pa.exponent;
  31. info.price.emaPrice = pa.emaPrice.value;
  32. info.price.emaConf = uint64(pa.emaConf.value);
  33. info.price.productId = pa.productId;
  34. // These aren't sent in the wire format yet
  35. info.price.numPublishers = 0;
  36. info.price.maxNumPublishers = 0;
  37. return info;
  38. }
  39. function verifyPythVM(IWormhole.VM memory vm) public view returns (bool valid) {
  40. if (vm.emitterChainId != pyth2WormholeChainId()) {
  41. return false;
  42. }
  43. if (vm.emitterAddress != pyth2WormholeEmitter()) {
  44. return false;
  45. }
  46. return true;
  47. }
  48. function parseBatchPriceAttestation(bytes memory encoded) public pure returns (PythStructs.BatchPriceAttestation memory bpa) {
  49. uint index = 0;
  50. // Check header
  51. bpa.header.magic = encoded.toUint32(index);
  52. index += 4;
  53. require(bpa.header.magic == 0x50325748, "invalid magic value");
  54. bpa.header.version = encoded.toUint16(index);
  55. index += 2;
  56. require(bpa.header.version == 2, "invalid version");
  57. bpa.header.payloadId = encoded.toUint8(index);
  58. index += 1;
  59. // Payload ID of 2 required for batch header
  60. require(bpa.header.payloadId == 2, "invalid payload ID");
  61. // Parse the number of attestations
  62. bpa.nAttestations = encoded.toUint16(index);
  63. index += 2;
  64. // Parse the attestation size
  65. bpa.attestationSize = encoded.toUint16(index);
  66. index += 2;
  67. require(encoded.length == (index + (bpa.attestationSize * bpa.nAttestations)), "invalid BatchPriceAttestation size");
  68. bpa.attestations = new PythStructs.PriceAttestation[](bpa.nAttestations);
  69. // Deserialize each attestation
  70. for (uint j=0; j < bpa.nAttestations; j++) {
  71. // Header
  72. bpa.attestations[j].header.magic = encoded.toUint32(index);
  73. index += 4;
  74. require(bpa.attestations[j].header.magic == 0x50325748, "invalid magic value");
  75. bpa.attestations[j].header.version = encoded.toUint16(index);
  76. index += 2;
  77. require(bpa.attestations[j].header.version == 2, "invalid version");
  78. bpa.attestations[j].header.payloadId = encoded.toUint8(index);
  79. index += 1;
  80. // Payload ID of 1 required for individual attestation
  81. require(bpa.attestations[j].header.payloadId == 1, "invalid payload ID");
  82. // Attestation
  83. bpa.attestations[j].productId = encoded.toBytes32(index);
  84. index += 32;
  85. bpa.attestations[j].priceId = encoded.toBytes32(index);
  86. index += 32;
  87. bpa.attestations[j].priceType = encoded.toUint8(index);
  88. index += 1;
  89. bpa.attestations[j].price = int64(encoded.toUint64(index));
  90. index += 8;
  91. bpa.attestations[j].exponent = int32(encoded.toUint32(index));
  92. index += 4;
  93. bpa.attestations[j].emaPrice.value = int64(encoded.toUint64(index));
  94. index += 8;
  95. bpa.attestations[j].emaPrice.numerator = int64(encoded.toUint64(index));
  96. index += 8;
  97. bpa.attestations[j].emaPrice.denominator = int64(encoded.toUint64(index));
  98. index += 8;
  99. bpa.attestations[j].emaConf.value = int64(encoded.toUint64(index));
  100. index += 8;
  101. bpa.attestations[j].emaConf.numerator = int64(encoded.toUint64(index));
  102. index += 8;
  103. bpa.attestations[j].emaConf.denominator = int64(encoded.toUint64(index));
  104. index += 8;
  105. bpa.attestations[j].confidenceInterval = encoded.toUint64(index);
  106. index += 8;
  107. bpa.attestations[j].status = encoded.toUint8(index);
  108. index += 1;
  109. bpa.attestations[j].corpAct = encoded.toUint8(index);
  110. index += 1;
  111. bpa.attestations[j].timestamp = encoded.toUint64(index);
  112. index += 8;
  113. }
  114. }
  115. function parsePriceAttestation(bytes memory encodedPriceAttestation) public pure returns (PythStructs.PriceAttestation memory pa) {
  116. uint index = 0;
  117. pa.header.magic = encodedPriceAttestation.toUint32(index);
  118. index += 4;
  119. require(pa.header.magic == 0x50325748, "invalid protocol");
  120. pa.header.version = encodedPriceAttestation.toUint16(index);
  121. index += 2;
  122. require(pa.header.version == 1, "invalid protocol");
  123. pa.header.payloadId = encodedPriceAttestation.toUint8(index);
  124. index += 1;
  125. require(pa.header.payloadId == 1, "invalid PriceAttestation");
  126. pa.productId = encodedPriceAttestation.toBytes32(index);
  127. index += 32;
  128. pa.priceId = encodedPriceAttestation.toBytes32(index);
  129. index += 32;
  130. pa.priceType = encodedPriceAttestation.toUint8(index);
  131. index += 1;
  132. pa.price = int64(encodedPriceAttestation.toUint64(index));
  133. index += 8;
  134. pa.exponent = int32(encodedPriceAttestation.toUint32(index));
  135. index += 4;
  136. pa.emaPrice.value = int64(encodedPriceAttestation.toUint64(index));
  137. index += 8;
  138. pa.emaPrice.numerator = int64(encodedPriceAttestation.toUint64(index));
  139. index += 8;
  140. pa.emaPrice.denominator = int64(encodedPriceAttestation.toUint64(index));
  141. index += 8;
  142. pa.emaConf.value = int64(encodedPriceAttestation.toUint64(index));
  143. index += 8;
  144. pa.emaConf.numerator = int64(encodedPriceAttestation.toUint64(index));
  145. index += 8;
  146. pa.emaConf.denominator = int64(encodedPriceAttestation.toUint64(index));
  147. index += 8;
  148. pa.confidenceInterval = encodedPriceAttestation.toUint64(index);
  149. index += 8;
  150. pa.status = encodedPriceAttestation.toUint8(index);
  151. index += 1;
  152. pa.corpAct = encodedPriceAttestation.toUint8(index);
  153. index += 1;
  154. pa.timestamp = encodedPriceAttestation.toUint64(index);
  155. index += 8;
  156. require(encodedPriceAttestation.length == index, "invalid PriceAttestation");
  157. }
  158. }