GasBenchmark.t.sol 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. // SPDX-License-Identifier: Apache 2
  2. pragma solidity ^0.8.0;
  3. import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
  4. import "forge-std/Test.sol";
  5. import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
  6. import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
  7. import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
  8. import "./utils/WormholeTestUtils.t.sol";
  9. import "./utils/PythTestUtils.t.sol";
  10. contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils {
  11. // 19, current mainnet number of guardians, is used to have gas estimates
  12. // close to our mainnet transactions.
  13. uint8 constant NUM_GUARDIANS = 19;
  14. // 2/3 of the guardians should sign a message for a VAA which is 13 out of 19 guardians.
  15. // It is possible to have more signers but the median seems to be 13.
  16. uint8 constant NUM_GUARDIAN_SIGNERS = 13;
  17. // We use 5 prices to form a batch of 5 prices, close to our mainnet transactions.
  18. uint8 constant NUM_PRICES = 5;
  19. IPyth public pyth;
  20. bytes32[] priceIds;
  21. // Cached prices are populated in the setUp
  22. PythStructs.Price[] cachedPrices;
  23. bytes[] cachedPricesUpdateData;
  24. uint cachedPricesUpdateFee;
  25. uint64[] cachedPricesPublishTimes;
  26. // Fresh prices are different prices that can be used
  27. // as a fresh price to update the prices
  28. PythStructs.Price[] freshPrices;
  29. bytes[] freshPricesUpdateData;
  30. uint freshPricesUpdateFee;
  31. uint64[] freshPricesPublishTimes;
  32. uint64 sequence;
  33. uint randSeed;
  34. function setUp() public {
  35. pyth = IPyth(setUpPyth(setUpWormhole(NUM_GUARDIANS)));
  36. priceIds = new bytes32[](NUM_PRICES);
  37. priceIds[0] = bytes32(
  38. 0x1000000000000000000000000000000000000000000000000000000000000f00
  39. );
  40. for (uint i = 1; i < NUM_PRICES; ++i) {
  41. priceIds[i] = bytes32(uint256(priceIds[i - 1]) + 1);
  42. }
  43. for (uint i = 0; i < NUM_PRICES; ++i) {
  44. uint64 publishTime = uint64(getRand() % 10);
  45. cachedPrices.push(
  46. PythStructs.Price(
  47. int64(uint64(getRand() % 1000)), // Price
  48. uint64(getRand() % 100), // Confidence
  49. -5, // Expo
  50. publishTime
  51. )
  52. );
  53. cachedPricesPublishTimes.push(publishTime);
  54. publishTime += uint64(getRand() % 10);
  55. freshPrices.push(
  56. PythStructs.Price(
  57. int64(uint64(getRand() % 1000)), // Price
  58. uint64(getRand() % 100), // Confidence
  59. -5, // Expo
  60. publishTime
  61. )
  62. );
  63. freshPricesPublishTimes.push(publishTime);
  64. }
  65. // Populate the contract with the initial prices
  66. (
  67. cachedPricesUpdateData,
  68. cachedPricesUpdateFee
  69. ) = generateUpdateDataAndFee(cachedPrices);
  70. pyth.updatePriceFeeds{value: cachedPricesUpdateFee}(
  71. cachedPricesUpdateData
  72. );
  73. (
  74. freshPricesUpdateData,
  75. freshPricesUpdateFee
  76. ) = generateUpdateDataAndFee(freshPrices);
  77. }
  78. function getRand() internal returns (uint val) {
  79. ++randSeed;
  80. val = uint(keccak256(abi.encode(randSeed)));
  81. }
  82. function generateUpdateDataAndFee(
  83. PythStructs.Price[] memory prices
  84. ) internal returns (bytes[] memory updateData, uint updateFee) {
  85. bytes memory vaa = generatePriceFeedUpdateVAA(
  86. pricesToPriceAttestations(priceIds, prices),
  87. sequence,
  88. NUM_GUARDIAN_SIGNERS
  89. );
  90. ++sequence;
  91. updateData = new bytes[](1);
  92. updateData[0] = vaa;
  93. updateFee = pyth.getUpdateFee(updateData);
  94. }
  95. function testBenchmarkUpdatePriceFeedsFresh() public {
  96. pyth.updatePriceFeeds{value: freshPricesUpdateFee}(
  97. freshPricesUpdateData
  98. );
  99. }
  100. function testBenchmarkUpdatePriceFeedsNotFresh() public {
  101. pyth.updatePriceFeeds{value: cachedPricesUpdateFee}(
  102. cachedPricesUpdateData
  103. );
  104. }
  105. function testBenchmarkUpdatePriceFeedsIfNecessaryFresh() public {
  106. // Since the prices have advanced, the publishTimes are newer than one in
  107. // the contract and hence, the call should succeed.
  108. pyth.updatePriceFeedsIfNecessary{value: freshPricesUpdateFee}(
  109. freshPricesUpdateData,
  110. priceIds,
  111. freshPricesPublishTimes
  112. );
  113. }
  114. function testBenchmarkUpdatePriceFeedsIfNecessaryNotFresh() public {
  115. // Since the price is not advanced, the publishTimes are the same as the
  116. // ones in the contract.
  117. vm.expectRevert(PythErrors.NoFreshUpdate.selector);
  118. pyth.updatePriceFeedsIfNecessary{value: cachedPricesUpdateFee}(
  119. cachedPricesUpdateData,
  120. priceIds,
  121. cachedPricesPublishTimes
  122. );
  123. }
  124. function testBenchmarkParsePriceFeedUpdatesForOnePriceFeed() public {
  125. bytes32[] memory ids = new bytes32[](1);
  126. ids[0] = priceIds[0];
  127. pyth.parsePriceFeedUpdates{value: freshPricesUpdateFee}(
  128. freshPricesUpdateData,
  129. ids,
  130. 0,
  131. 50
  132. );
  133. }
  134. function testBenchmarkParsePriceFeedUpdatesForTwoPriceFeed() public {
  135. bytes32[] memory ids = new bytes32[](2);
  136. ids[0] = priceIds[0];
  137. ids[1] = priceIds[1];
  138. pyth.parsePriceFeedUpdates{value: freshPricesUpdateFee}(
  139. freshPricesUpdateData,
  140. ids,
  141. 0,
  142. 50
  143. );
  144. }
  145. function testBenchmarkParsePriceFeedUpdatesForOnePriceFeedNotWithinRange()
  146. public
  147. {
  148. bytes32[] memory ids = new bytes32[](1);
  149. ids[0] = priceIds[0];
  150. vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector);
  151. pyth.parsePriceFeedUpdates{value: freshPricesUpdateFee}(
  152. freshPricesUpdateData,
  153. ids,
  154. 50,
  155. 100
  156. );
  157. }
  158. function testBenchmarkGetPrice() public {
  159. // Set the block timestamp to 0. As prices have < 10 timestamp and staleness
  160. // is set to 60 seconds, the getPrice should work as expected.
  161. vm.warp(0);
  162. pyth.getPrice(priceIds[0]);
  163. }
  164. function testBenchmarkGetEmaPrice() public {
  165. // Set the block timestamp to 0. As prices have < 10 timestamp and staleness
  166. // is set to 60 seconds, the getPrice should work as expected.
  167. vm.warp(0);
  168. pyth.getEmaPrice(priceIds[0]);
  169. }
  170. function testBenchmarkGetUpdateFee() public view {
  171. pyth.getUpdateFee(freshPricesUpdateData);
  172. }
  173. }