Pyth.sol 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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. contract Pyth is PythGetters, PythSetters {
  9. using BytesLib for bytes;
  10. function initialize(
  11. uint16 chainId,
  12. address wormhole,
  13. uint16 pyth2WormholeChainId,
  14. bytes32 pyth2WormholeEmitter
  15. ) virtual public {
  16. setChainId(chainId);
  17. setWormhole(wormhole);
  18. setPyth2WormholeChainId(pyth2WormholeChainId);
  19. setPyth2WormholeEmitter(pyth2WormholeEmitter);
  20. }
  21. function attestPriceBatch(bytes memory encodedVm) public returns (PythStructs.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. PythStructs.BatchPriceAttestation memory batch = parseBatchPriceAttestation(vm.payload);
  26. for (uint i = 0; i < batch.attestations.length; i++) {
  27. PythStructs.PriceAttestation memory attestation = batch.attestations[i];
  28. PythStructs.PriceInfo memory latestPrice = latestPriceInfo(attestation.priceId);
  29. if(attestation.timestamp > latestPrice.attestationTime) {
  30. setLatestPriceInfo(attestation.priceId, newPriceInfo(attestation));
  31. }
  32. }
  33. return batch;
  34. }
  35. function newPriceInfo(PythStructs.PriceAttestation memory pa) private view returns (PythStructs.PriceInfo memory info) {
  36. info.attestationTime = pa.timestamp;
  37. info.arrivalTime = block.timestamp;
  38. info.arrivalBlock = block.number;
  39. info.price.id = pa.priceId;
  40. info.price.price = pa.price;
  41. info.price.conf = pa.confidenceInterval;
  42. info.price.status = PythSDK.PriceStatus(pa.status);
  43. info.price.expo = pa.exponent;
  44. info.price.emaPrice = pa.emaPrice.value;
  45. info.price.emaConf = uint64(pa.emaConf.value);
  46. info.price.productId = pa.productId;
  47. // These aren't sent in the wire format yet
  48. info.price.numPublishers = 0;
  49. info.price.maxNumPublishers = 0;
  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 (PythStructs.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.version = encoded.toUint16(index);
  68. index += 2;
  69. require(bpa.header.version == 2, "invalid version");
  70. bpa.header.payloadId = encoded.toUint8(index);
  71. index += 1;
  72. // Payload ID of 2 required for batch header
  73. require(bpa.header.payloadId == 2, "invalid payload ID");
  74. // Parse the number of attestations
  75. bpa.nAttestations = encoded.toUint16(index);
  76. index += 2;
  77. // Parse the attestation size
  78. bpa.attestationSize = encoded.toUint16(index);
  79. index += 2;
  80. require(encoded.length == (index + (bpa.attestationSize * bpa.nAttestations)), "invalid BatchPriceAttestation size");
  81. bpa.attestations = new PythStructs.PriceAttestation[](bpa.nAttestations);
  82. // Deserialize each attestation
  83. for (uint j=0; j < bpa.nAttestations; j++) {
  84. // Header
  85. bpa.attestations[j].header.magic = encoded.toUint32(index);
  86. index += 4;
  87. require(bpa.attestations[j].header.magic == 0x50325748, "invalid magic value");
  88. bpa.attestations[j].header.version = encoded.toUint16(index);
  89. index += 2;
  90. require(bpa.attestations[j].header.version == 2, "invalid version");
  91. bpa.attestations[j].header.payloadId = encoded.toUint8(index);
  92. index += 1;
  93. // Payload ID of 1 required for individual attestation
  94. require(bpa.attestations[j].header.payloadId == 1, "invalid payload ID");
  95. // Attestation
  96. bpa.attestations[j].productId = encoded.toBytes32(index);
  97. index += 32;
  98. bpa.attestations[j].priceId = encoded.toBytes32(index);
  99. index += 32;
  100. bpa.attestations[j].priceType = encoded.toUint8(index);
  101. index += 1;
  102. bpa.attestations[j].price = int64(encoded.toUint64(index));
  103. index += 8;
  104. bpa.attestations[j].exponent = int32(encoded.toUint32(index));
  105. index += 4;
  106. bpa.attestations[j].emaPrice.value = int64(encoded.toUint64(index));
  107. index += 8;
  108. bpa.attestations[j].emaPrice.numerator = int64(encoded.toUint64(index));
  109. index += 8;
  110. bpa.attestations[j].emaPrice.denominator = int64(encoded.toUint64(index));
  111. index += 8;
  112. bpa.attestations[j].emaConf.value = int64(encoded.toUint64(index));
  113. index += 8;
  114. bpa.attestations[j].emaConf.numerator = int64(encoded.toUint64(index));
  115. index += 8;
  116. bpa.attestations[j].emaConf.denominator = int64(encoded.toUint64(index));
  117. index += 8;
  118. bpa.attestations[j].confidenceInterval = encoded.toUint64(index);
  119. index += 8;
  120. bpa.attestations[j].status = encoded.toUint8(index);
  121. index += 1;
  122. bpa.attestations[j].corpAct = encoded.toUint8(index);
  123. index += 1;
  124. bpa.attestations[j].timestamp = encoded.toUint64(index);
  125. index += 8;
  126. }
  127. }
  128. }