Pyth.sol 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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. abstract 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 calldata encodedVm) private 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 data source chain/emitter ID");
  25. PythInternalStructs.BatchPriceAttestation memory batch = parseBatchPriceAttestation(vm.payload);
  26. uint freshPrices = 0;
  27. for (uint i = 0; i < batch.attestations.length; i++) {
  28. PythInternalStructs.PriceAttestation memory attestation = batch.attestations[i];
  29. PythInternalStructs.PriceInfo memory newPriceInfo = createNewPriceInfo(attestation);
  30. PythInternalStructs.PriceInfo memory latestPrice = latestPriceInfo(attestation.priceId);
  31. bool fresh = false;
  32. if(newPriceInfo.priceFeed.price.publishTime > latestPrice.priceFeed.price.publishTime) {
  33. freshPrices += 1;
  34. fresh = true;
  35. setLatestPriceInfo(attestation.priceId, newPriceInfo);
  36. }
  37. emit PriceFeedUpdate(attestation.priceId, fresh, vm.emitterChainId, vm.sequence, latestPrice.priceFeed.price.publishTime,
  38. newPriceInfo.priceFeed.price.publishTime, newPriceInfo.priceFeed.price.price, newPriceInfo.priceFeed.price.conf);
  39. }
  40. emit BatchPriceFeedUpdate(vm.emitterChainId, vm.sequence, batch.attestations.length, freshPrices);
  41. return batch;
  42. }
  43. function updatePriceFeeds(bytes[] calldata updateData) public override payable {
  44. uint requiredFee = getUpdateFee(updateData.length);
  45. require(msg.value >= requiredFee, "insufficient paid fee amount");
  46. payable(msg.sender).transfer(msg.value - requiredFee);
  47. for(uint i = 0; i < updateData.length; i++) {
  48. updatePriceBatchFromVm(updateData[i]);
  49. }
  50. emit UpdatePriceFeeds(msg.sender, updateData.length, requiredFee);
  51. }
  52. function getUpdateFee(uint updateDataSize) public override view returns (uint feeAmount) {
  53. return singleUpdateFeeInWei() * updateDataSize;
  54. }
  55. function createNewPriceInfo(PythInternalStructs.PriceAttestation memory pa) private view returns (PythInternalStructs.PriceInfo memory info) {
  56. info.attestationTime = pa.attestationTime;
  57. info.arrivalTime = block.timestamp;
  58. info.arrivalBlock = block.number;
  59. info.priceFeed.id = pa.priceId;
  60. PythInternalStructs.PriceAttestationStatus status = PythInternalStructs.PriceAttestationStatus(pa.status);
  61. if (status == PythInternalStructs.PriceAttestationStatus.TRADING) {
  62. info.priceFeed.price.price = pa.price;
  63. info.priceFeed.price.conf = pa.conf;
  64. info.priceFeed.price.publishTime = pa.publishTime;
  65. info.priceFeed.emaPrice.publishTime = pa.publishTime;
  66. } else {
  67. info.priceFeed.price.price = pa.prevPrice;
  68. info.priceFeed.price.conf = pa.prevConf;
  69. info.priceFeed.price.publishTime = pa.prevPublishTime;
  70. // The EMA is last updated when the aggregate had trading status,
  71. // so, we use prev_publish_time (the time when the aggregate last had trading status).
  72. info.priceFeed.emaPrice.publishTime = pa.prevPublishTime;
  73. }
  74. info.priceFeed.price.expo = pa.expo;
  75. info.priceFeed.emaPrice.price = pa.emaPrice;
  76. info.priceFeed.emaPrice.conf = pa.emaConf;
  77. info.priceFeed.emaPrice.expo = pa.expo;
  78. return info;
  79. }
  80. function verifyPythVM(IWormhole.VM memory vm) private view returns (bool valid) {
  81. return isValidDataSource(vm.emitterChainId, vm.emitterAddress);
  82. }
  83. function parseBatchPriceAttestation(bytes memory encoded) public pure returns (PythInternalStructs.BatchPriceAttestation memory bpa) {
  84. uint index = 0;
  85. // Check header
  86. bpa.header.magic = encoded.toUint32(index);
  87. index += 4;
  88. require(bpa.header.magic == 0x50325748, "invalid magic value");
  89. bpa.header.versionMajor = encoded.toUint16(index);
  90. index += 2;
  91. require(bpa.header.versionMajor == 3, "invalid version major, expected 3");
  92. bpa.header.versionMinor = encoded.toUint16(index);
  93. index += 2;
  94. require(bpa.header.versionMinor >= 0, "invalid version minor, expected 0 or more");
  95. bpa.header.hdrSize = encoded.toUint16(index);
  96. index += 2;
  97. // NOTE(2022-04-19): Currently, only payloadId comes after
  98. // hdrSize. Future extra header fields must be read using a
  99. // separate offset to respect hdrSize, i.e.:
  100. //
  101. // uint hdrIndex = 0;
  102. // bpa.header.payloadId = encoded.toUint8(index + hdrIndex);
  103. // hdrIndex += 1;
  104. //
  105. // bpa.header.someNewField = encoded.toUint32(index + hdrIndex);
  106. // hdrIndex += 4;
  107. //
  108. // // Skip remaining unknown header bytes
  109. // index += bpa.header.hdrSize;
  110. bpa.header.payloadId = encoded.toUint8(index);
  111. // Skip remaining unknown header bytes
  112. index += bpa.header.hdrSize;
  113. // Payload ID of 2 required for batch headerBa
  114. require(bpa.header.payloadId == 2, "invalid payload ID, expected 2 for BatchPriceAttestation");
  115. // Parse the number of attestations
  116. bpa.nAttestations = encoded.toUint16(index);
  117. index += 2;
  118. // Parse the attestation size
  119. bpa.attestationSize = encoded.toUint16(index);
  120. index += 2;
  121. require(encoded.length == (index + (bpa.attestationSize * bpa.nAttestations)), "invalid BatchPriceAttestation size");
  122. bpa.attestations = new PythInternalStructs.PriceAttestation[](bpa.nAttestations);
  123. // Deserialize each attestation
  124. for (uint j=0; j < bpa.nAttestations; j++) {
  125. // NOTE: We don't advance the global index immediately.
  126. // attestationIndex is an attestation-local offset used
  127. // for readability and easier debugging.
  128. uint attestationIndex = 0;
  129. // Attestation
  130. bpa.attestations[j].productId = encoded.toBytes32(index + attestationIndex);
  131. attestationIndex += 32;
  132. bpa.attestations[j].priceId = encoded.toBytes32(index + attestationIndex);
  133. attestationIndex += 32;
  134. bpa.attestations[j].price = int64(encoded.toUint64(index + attestationIndex));
  135. attestationIndex += 8;
  136. bpa.attestations[j].conf = encoded.toUint64(index + attestationIndex);
  137. attestationIndex += 8;
  138. bpa.attestations[j].expo = int32(encoded.toUint32(index + attestationIndex));
  139. attestationIndex += 4;
  140. bpa.attestations[j].emaPrice = int64(encoded.toUint64(index + attestationIndex));
  141. attestationIndex += 8;
  142. bpa.attestations[j].emaConf = encoded.toUint64(index + attestationIndex);
  143. attestationIndex += 8;
  144. bpa.attestations[j].status = encoded.toUint8(index + attestationIndex);
  145. attestationIndex += 1;
  146. bpa.attestations[j].numPublishers = encoded.toUint32(index + attestationIndex);
  147. attestationIndex += 4;
  148. bpa.attestations[j].maxNumPublishers = encoded.toUint32(index + attestationIndex);
  149. attestationIndex += 4;
  150. bpa.attestations[j].attestationTime = encoded.toUint64(index + attestationIndex);
  151. attestationIndex += 8;
  152. bpa.attestations[j].publishTime = encoded.toUint64(index + attestationIndex);
  153. attestationIndex += 8;
  154. bpa.attestations[j].prevPublishTime = encoded.toUint64(index + attestationIndex);
  155. attestationIndex += 8;
  156. bpa.attestations[j].prevPrice = int64(encoded.toUint64(index + attestationIndex));
  157. attestationIndex += 8;
  158. bpa.attestations[j].prevConf = encoded.toUint64(index + attestationIndex);
  159. attestationIndex += 8;
  160. require(attestationIndex <= bpa.attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes");
  161. // Respect specified attestation size for forward-compat
  162. index += bpa.attestationSize;
  163. }
  164. }
  165. function queryPriceFeed(bytes32 id) public view override returns (PythStructs.PriceFeed memory priceFeed){
  166. // Look up the latest price info for the given ID
  167. PythInternalStructs.PriceInfo memory info = latestPriceInfo(id);
  168. require(info.priceFeed.id != 0, "no price feed found for the given price id");
  169. return info.priceFeed;
  170. }
  171. function priceFeedExists(bytes32 id) public override view returns (bool) {
  172. PythInternalStructs.PriceInfo memory info = latestPriceInfo(id);
  173. return (info.priceFeed.id != 0);
  174. }
  175. function getValidTimePeriod() public override view returns (uint) {
  176. return validTimePeriodSeconds();
  177. }
  178. function isPyth() internal pure returns (bool) {
  179. return true;
  180. }
  181. function version() public pure returns (string memory) {
  182. return "1.1.0";
  183. }
  184. function deployCommitHash() public pure returns (string memory) {
  185. // This is a place holder for the commit hash and will be replaced
  186. // with the commit hash upon deployment.
  187. return "__DEPLOY_COMMIT_HASH_PLACEHOLER__";
  188. }
  189. }