PythTestUtils.t.sol 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. // SPDX-License-Identifier: Apache 2
  2. pragma solidity ^0.8.0;
  3. import "../../contracts/pyth/PythUpgradable.sol";
  4. import "../../contracts/pyth/PythInternalStructs.sol";
  5. import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
  6. import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
  7. import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
  8. import "forge-std/Test.sol";
  9. import "./WormholeTestUtils.t.sol";
  10. abstract contract PythTestUtils is Test, WormholeTestUtils {
  11. uint16 constant SOURCE_EMITTER_CHAIN_ID = 0x1;
  12. bytes32 constant SOURCE_EMITTER_ADDRESS = 0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b;
  13. uint16 constant GOVERNANCE_EMITTER_CHAIN_ID = 0x1;
  14. bytes32 constant GOVERNANCE_EMITTER_ADDRESS = 0x0000000000000000000000000000000000000000000000000000000000000011;
  15. function setUpPyth(address wormhole) public returns (address) {
  16. PythUpgradable implementation = new PythUpgradable();
  17. ERC1967Proxy proxy = new ERC1967Proxy(address(implementation), new bytes(0));
  18. PythUpgradable pyth = PythUpgradable(address(proxy));
  19. pyth.initialize(
  20. wormhole,
  21. SOURCE_EMITTER_CHAIN_ID,
  22. SOURCE_EMITTER_ADDRESS
  23. );
  24. // TODO: All the logic below should be moved to the initializer
  25. pyth.addDataSource(
  26. SOURCE_EMITTER_CHAIN_ID,
  27. SOURCE_EMITTER_ADDRESS
  28. );
  29. pyth.updateSingleUpdateFeeInWei(
  30. 1
  31. );
  32. pyth.updateValidTimePeriodSeconds(
  33. 60
  34. );
  35. pyth.updateGovernanceDataSource(
  36. GOVERNANCE_EMITTER_CHAIN_ID,
  37. GOVERNANCE_EMITTER_ADDRESS,
  38. 0
  39. );
  40. return address(pyth);
  41. }
  42. // Generates byte-encoded payload for the given prices. It sets the emaPrice the same
  43. // as the given price. You can use this to mock wormhole call using `vm.mockCall` and
  44. // return a VM struct with this payload.
  45. // You can use generatePriceFeedUpdateVAA to generate a VAA for a price update.
  46. function generatePriceFeedUpdatePayload(
  47. bytes32[] memory priceIds,
  48. PythStructs.Price[] memory prices
  49. ) public returns (bytes memory payload) {
  50. assertEq(priceIds.length, prices.length);
  51. bytes memory attestations = new bytes(0);
  52. for (uint i = 0; i < prices.length; ++i) {
  53. // encodePacked uses padding for arrays and we don't want it, so we manually concat them.
  54. attestations = abi.encodePacked(
  55. attestations,
  56. priceIds[i], // Product ID, we use the same price Id. This field is not used.
  57. priceIds[i], // Price ID,
  58. prices[i].price, // Price
  59. prices[i].conf, // Confidence
  60. prices[i].expo, // Exponent
  61. prices[i].price, // EMA price
  62. prices[i].conf // EMA confidence
  63. );
  64. // Breaking this in two encodePackes because of the limited EVM stack.
  65. attestations = abi.encodePacked(
  66. attestations,
  67. uint8(PythInternalStructs.PriceAttestationStatus.TRADING),
  68. uint32(5), // Number of publishers. This field is not used.
  69. uint32(10), // Maximum number of publishers. This field is not used.
  70. uint64(prices[i].publishTime), // Attestation time. This field is not used.
  71. uint64(prices[i].publishTime), // Publish time.
  72. // Previous values are unused as status is trading. We use the same value
  73. // to make sure the test is irrelevant of the logic of which price is chosen.
  74. uint64(prices[i].publishTime), // Previous publish time.
  75. prices[i].price, // Previous price
  76. prices[i].conf // Previous confidence
  77. );
  78. }
  79. payload = abi.encodePacked(
  80. uint32(0x50325748), // Magic
  81. uint16(3), // Major version
  82. uint16(0), // Minor version
  83. uint16(1), // Header size of 1 byte as it only contains payloadId
  84. uint8(2), // Payload ID 2 means it's a batch price attestation
  85. uint16(prices.length), // Number of attestations
  86. uint16(attestations.length / prices.length), // Size of a single price attestation.
  87. attestations
  88. );
  89. }
  90. // Generates a VAA for the given prices.
  91. // This method calls generatePriceFeedUpdatePayload and then creates a VAA with it.
  92. // The VAAs generated from this method use block timestamp as their timestamp.
  93. function generatePriceFeedUpdateVAA(
  94. bytes32[] memory priceIds,
  95. PythStructs.Price[] memory prices,
  96. uint64 sequence,
  97. uint8 numSigners
  98. ) public returns (bytes memory vaa) {
  99. bytes memory payload = generatePriceFeedUpdatePayload(
  100. priceIds,
  101. prices
  102. );
  103. vaa = generateVaa(
  104. uint32(block.timestamp),
  105. SOURCE_EMITTER_CHAIN_ID,
  106. SOURCE_EMITTER_ADDRESS,
  107. sequence,
  108. payload,
  109. numSigners
  110. );
  111. }
  112. }
  113. contract PythTestUtilsTest is Test, WormholeTestUtils, PythTestUtils {
  114. // TODO: It is better to have a PythEvents contract that be extendable.
  115. event PriceFeedUpdate(bytes32 indexed id, bool indexed fresh, uint16 chainId, uint64 sequenceNumber, uint lastPublishTime, uint publishTime, int64 price, uint64 conf);
  116. function testGeneratePriceFeedUpdateVAAWorks() public {
  117. IPyth pyth = IPyth(setUpPyth(setUpWormhole(
  118. 1 // Number of guardians
  119. )));
  120. bytes32[] memory priceIds = new bytes32[](1);
  121. priceIds[0] = 0x0000000000000000000000000000000000000000000000000000000000000222;
  122. PythStructs.Price[] memory prices = new PythStructs.Price[](1);
  123. prices[0] = PythStructs.Price(
  124. 100, // Price
  125. 10, // Confidence
  126. -5, // Exponent
  127. 1 // Publish time
  128. );
  129. bytes memory vaa = generatePriceFeedUpdateVAA(
  130. priceIds,
  131. prices,
  132. 1, // Sequence
  133. 1 // No. Signers
  134. );
  135. bytes[] memory updateData = new bytes[](1);
  136. updateData[0] = vaa;
  137. uint updateFee = pyth.getUpdateFee(updateData);
  138. vm.expectEmit(true, true, false, true);
  139. emit PriceFeedUpdate(priceIds[0], true, SOURCE_EMITTER_CHAIN_ID, 1, 0, 1, 100, 10);
  140. pyth.updatePriceFeeds{value: updateFee}(updateData);
  141. }
  142. }