GasBenchmark.t.sol 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  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. // We will have less than 512 price for a foreseeable future.
  20. uint8 constant MERKLE_TREE_DEPTH = 9;
  21. IPyth public pyth;
  22. bytes32[] priceIds;
  23. // Cached prices are populated in the setUp
  24. PythStructs.Price[] cachedPrices;
  25. bytes[] cachedPricesWhBatchUpdateData;
  26. uint cachedPricesWhBatchUpdateFee;
  27. uint64[] cachedPricesPublishTimes;
  28. bytes[][] cachedPricesWhMerkleUpdateData; // i th element contains the update data for the first i prices
  29. uint[] cachedPricesWhMerkleUpdateFee; // i th element contains the update fee for the first i prices
  30. // Fresh prices are different prices that can be used
  31. // as a fresh price to update the prices
  32. PythStructs.Price[] freshPrices;
  33. bytes[] freshPricesWhBatchUpdateData;
  34. uint freshPricesWhBatchUpdateFee;
  35. uint64[] freshPricesPublishTimes;
  36. bytes[][] freshPricesWhMerkleUpdateData; // i th element contains the update data for the first i prices
  37. uint[] freshPricesWhMerkleUpdateFee; // i th element contains the update fee for the first i prices
  38. uint64 sequence;
  39. uint randSeed;
  40. function setUp() public {
  41. pyth = IPyth(setUpPyth(setUpWormhole(NUM_GUARDIANS)));
  42. priceIds = new bytes32[](NUM_PRICES);
  43. priceIds[0] = bytes32(
  44. 0x1000000000000000000000000000000000000000000000000000000000000f00
  45. );
  46. for (uint i = 1; i < NUM_PRICES; ++i) {
  47. priceIds[i] = bytes32(uint256(priceIds[i - 1]) + 1);
  48. }
  49. for (uint i = 0; i < NUM_PRICES; ++i) {
  50. uint64 publishTime = uint64(getRand() % 10);
  51. cachedPrices.push(
  52. PythStructs.Price(
  53. int64(uint64(getRand() % 1000)), // Price
  54. uint64(getRand() % 100), // Confidence
  55. -5, // Expo
  56. publishTime
  57. )
  58. );
  59. cachedPricesPublishTimes.push(publishTime);
  60. publishTime += uint64(getRand() % 10);
  61. freshPrices.push(
  62. PythStructs.Price(
  63. int64(uint64(getRand() % 1000)), // Price
  64. uint64(getRand() % 100), // Confidence
  65. -5, // Expo
  66. publishTime
  67. )
  68. );
  69. freshPricesPublishTimes.push(publishTime);
  70. // Generate Wormhole Merkle update data and fee for the first i th prices
  71. (
  72. bytes[] memory updateData,
  73. uint updateFee
  74. ) = generateWhMerkleUpdateDataAndFee(cachedPrices);
  75. cachedPricesWhMerkleUpdateData.push(updateData);
  76. cachedPricesWhMerkleUpdateFee.push(updateFee);
  77. (updateData, updateFee) = generateWhMerkleUpdateDataAndFee(
  78. freshPrices
  79. );
  80. freshPricesWhMerkleUpdateData.push(updateData);
  81. freshPricesWhMerkleUpdateFee.push(updateFee);
  82. }
  83. // Populate the contract with the initial prices
  84. (
  85. cachedPricesWhBatchUpdateData,
  86. cachedPricesWhBatchUpdateFee
  87. ) = generateWhBatchUpdateDataAndFee(cachedPrices);
  88. pyth.updatePriceFeeds{value: cachedPricesWhBatchUpdateFee}(
  89. cachedPricesWhBatchUpdateData
  90. );
  91. (
  92. freshPricesWhBatchUpdateData,
  93. freshPricesWhBatchUpdateFee
  94. ) = generateWhBatchUpdateDataAndFee(freshPrices);
  95. }
  96. function getRand() internal returns (uint val) {
  97. ++randSeed;
  98. val = uint(keccak256(abi.encode(randSeed)));
  99. }
  100. function generateWhBatchUpdateDataAndFee(
  101. PythStructs.Price[] memory prices
  102. ) internal returns (bytes[] memory updateData, uint updateFee) {
  103. bytes memory vaa = generateWhBatchUpdate(
  104. pricesToPriceAttestations(priceIds, prices),
  105. sequence,
  106. NUM_GUARDIAN_SIGNERS
  107. );
  108. ++sequence;
  109. updateData = new bytes[](1);
  110. updateData[0] = vaa;
  111. updateFee = pyth.getUpdateFee(updateData);
  112. }
  113. function generateWhMerkleUpdateDataAndFee(
  114. PythStructs.Price[] memory prices
  115. ) internal returns (bytes[] memory updateData, uint updateFee) {
  116. updateData = new bytes[](1);
  117. updateData[0] = generateWhMerkleUpdate(
  118. pricesToPriceFeedMessages(priceIds, prices),
  119. MERKLE_TREE_DEPTH,
  120. NUM_GUARDIAN_SIGNERS
  121. );
  122. updateFee = pyth.getUpdateFee(updateData);
  123. }
  124. function testBenchmarkUpdatePriceFeedsWhBatchFresh() public {
  125. pyth.updatePriceFeeds{value: freshPricesWhBatchUpdateFee}(
  126. freshPricesWhBatchUpdateData
  127. );
  128. }
  129. function testBenchmarkUpdatePriceFeedsWhBatchNotFresh() public {
  130. pyth.updatePriceFeeds{value: cachedPricesWhBatchUpdateFee}(
  131. cachedPricesWhBatchUpdateData
  132. );
  133. }
  134. function testBenchmarkUpdatePriceFeedsWhMerkle1FeedFresh() public {
  135. pyth.updatePriceFeeds{value: freshPricesWhMerkleUpdateFee[0]}(
  136. freshPricesWhMerkleUpdateData[0]
  137. );
  138. }
  139. function testBenchmarkUpdatePriceFeedsWhMerkle2FeedsFresh() public {
  140. pyth.updatePriceFeeds{value: freshPricesWhMerkleUpdateFee[1]}(
  141. freshPricesWhMerkleUpdateData[1]
  142. );
  143. }
  144. function testBenchmarkUpdatePriceFeedsWhMerkle3FeedsFresh() public {
  145. pyth.updatePriceFeeds{value: freshPricesWhMerkleUpdateFee[2]}(
  146. freshPricesWhMerkleUpdateData[2]
  147. );
  148. }
  149. function testBenchmarkUpdatePriceFeedsWhMerkle4FeedsFresh() public {
  150. pyth.updatePriceFeeds{value: freshPricesWhMerkleUpdateFee[3]}(
  151. freshPricesWhMerkleUpdateData[3]
  152. );
  153. }
  154. function testBenchmarkUpdatePriceFeedsWhMerkle5FeedsFresh() public {
  155. pyth.updatePriceFeeds{value: freshPricesWhMerkleUpdateFee[4]}(
  156. freshPricesWhMerkleUpdateData[4]
  157. );
  158. }
  159. function testBenchmarkUpdatePriceFeedsWhMerkle1FeedNotFresh() public {
  160. pyth.updatePriceFeeds{value: cachedPricesWhMerkleUpdateFee[0]}(
  161. cachedPricesWhMerkleUpdateData[0]
  162. );
  163. }
  164. function testBenchmarkUpdatePriceFeedsWhMerkle2FeedsNotFresh() public {
  165. pyth.updatePriceFeeds{value: cachedPricesWhMerkleUpdateFee[1]}(
  166. cachedPricesWhMerkleUpdateData[1]
  167. );
  168. }
  169. function testBenchmarkUpdatePriceFeedsWhMerkle3FeedsNotFresh() public {
  170. pyth.updatePriceFeeds{value: cachedPricesWhMerkleUpdateFee[2]}(
  171. cachedPricesWhMerkleUpdateData[2]
  172. );
  173. }
  174. function testBenchmarkUpdatePriceFeedsWhMerkle4FeedsNotFresh() public {
  175. pyth.updatePriceFeeds{value: cachedPricesWhMerkleUpdateFee[3]}(
  176. cachedPricesWhMerkleUpdateData[3]
  177. );
  178. }
  179. function testBenchmarkUpdatePriceFeedsWhMerkle5FeedsNotFresh() public {
  180. pyth.updatePriceFeeds{value: cachedPricesWhMerkleUpdateFee[4]}(
  181. cachedPricesWhMerkleUpdateData[4]
  182. );
  183. }
  184. function testBenchmarkUpdatePriceFeedsIfNecessaryWhBatchFresh() public {
  185. // Since the prices have advanced, the publishTimes are newer than one in
  186. // the contract and hence, the call should succeed.
  187. pyth.updatePriceFeedsIfNecessary{value: freshPricesWhBatchUpdateFee}(
  188. freshPricesWhBatchUpdateData,
  189. priceIds,
  190. freshPricesPublishTimes
  191. );
  192. }
  193. function testBenchmarkUpdatePriceFeedsIfNecessaryWhBatchNotFresh() public {
  194. // Since the price is not advanced, the publishTimes are the same as the
  195. // ones in the contract.
  196. vm.expectRevert(PythErrors.NoFreshUpdate.selector);
  197. pyth.updatePriceFeedsIfNecessary{value: cachedPricesWhBatchUpdateFee}(
  198. cachedPricesWhBatchUpdateData,
  199. priceIds,
  200. cachedPricesPublishTimes
  201. );
  202. }
  203. function testBenchmarkParsePriceFeedUpdatesForOnePriceFeed() public {
  204. bytes32[] memory ids = new bytes32[](1);
  205. ids[0] = priceIds[0];
  206. pyth.parsePriceFeedUpdates{value: freshPricesWhBatchUpdateFee}(
  207. freshPricesWhBatchUpdateData,
  208. ids,
  209. 0,
  210. 50
  211. );
  212. }
  213. function testBenchmarkParsePriceFeedUpdatesForTwoPriceFeed() public {
  214. bytes32[] memory ids = new bytes32[](2);
  215. ids[0] = priceIds[0];
  216. ids[1] = priceIds[1];
  217. pyth.parsePriceFeedUpdates{value: freshPricesWhBatchUpdateFee}(
  218. freshPricesWhBatchUpdateData,
  219. ids,
  220. 0,
  221. 50
  222. );
  223. }
  224. function testBenchmarkParsePriceFeedUpdatesForWhMerkle1() public {
  225. uint numIds = 1;
  226. bytes32[] memory ids = new bytes32[](numIds);
  227. for (uint i = 0; i < numIds; i++) {
  228. ids[i] = priceIds[i];
  229. }
  230. pyth.parsePriceFeedUpdates{
  231. value: freshPricesWhMerkleUpdateFee[numIds - 1]
  232. }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50);
  233. }
  234. function testBenchmarkParsePriceFeedUpdatesForWhMerkle2() public {
  235. uint numIds = 2;
  236. bytes32[] memory ids = new bytes32[](numIds);
  237. for (uint i = 0; i < numIds; i++) {
  238. ids[i] = priceIds[i];
  239. }
  240. pyth.parsePriceFeedUpdates{
  241. value: freshPricesWhMerkleUpdateFee[numIds - 1]
  242. }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50);
  243. }
  244. function testBenchmarkParsePriceFeedUpdatesForWhMerkle3() public {
  245. uint numIds = 3;
  246. bytes32[] memory ids = new bytes32[](numIds);
  247. for (uint i = 0; i < numIds; i++) {
  248. ids[i] = priceIds[i];
  249. }
  250. pyth.parsePriceFeedUpdates{
  251. value: freshPricesWhMerkleUpdateFee[numIds - 1]
  252. }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50);
  253. }
  254. function testBenchmarkParsePriceFeedUpdatesForWhMerkle4() public {
  255. uint numIds = 4;
  256. bytes32[] memory ids = new bytes32[](numIds);
  257. for (uint i = 0; i < numIds; i++) {
  258. ids[i] = priceIds[i];
  259. }
  260. pyth.parsePriceFeedUpdates{
  261. value: freshPricesWhMerkleUpdateFee[numIds - 1]
  262. }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50);
  263. }
  264. function testBenchmarkParsePriceFeedUpdatesForWhMerkle5() public {
  265. uint numIds = 5;
  266. bytes32[] memory ids = new bytes32[](numIds);
  267. for (uint i = 0; i < numIds; i++) {
  268. ids[i] = priceIds[i];
  269. }
  270. pyth.parsePriceFeedUpdates{
  271. value: freshPricesWhMerkleUpdateFee[numIds - 1]
  272. }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50);
  273. }
  274. function testBenchmarkParsePriceFeedUpdatesForAllPriceFeedsShuffledSubsetPriceIds()
  275. public
  276. {
  277. uint numIds = 3;
  278. bytes32[] memory ids = new bytes32[](numIds);
  279. ids[0] = priceIds[4];
  280. ids[1] = priceIds[2];
  281. ids[2] = priceIds[0];
  282. pyth.parsePriceFeedUpdates{value: freshPricesWhMerkleUpdateFee[4]}( // updateFee based on number of priceFeeds in updateData
  283. freshPricesWhMerkleUpdateData[4],
  284. ids,
  285. 0,
  286. 50
  287. );
  288. }
  289. function testBenchmarkParsePriceFeedUpdatesWhMerkleForOnePriceFeedNotWithinRange()
  290. public
  291. {
  292. bytes32[] memory ids = new bytes32[](1);
  293. ids[0] = priceIds[0];
  294. vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector);
  295. pyth.parsePriceFeedUpdates{value: freshPricesWhMerkleUpdateFee[0]}(
  296. freshPricesWhMerkleUpdateData[0],
  297. ids,
  298. 50,
  299. 100
  300. );
  301. }
  302. function testBenchmarkParsePriceFeedUpdatesForOnePriceFeedNotWithinRange()
  303. public
  304. {
  305. bytes32[] memory ids = new bytes32[](1);
  306. ids[0] = priceIds[0];
  307. vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector);
  308. pyth.parsePriceFeedUpdates{value: freshPricesWhBatchUpdateFee}(
  309. freshPricesWhBatchUpdateData,
  310. ids,
  311. 50,
  312. 100
  313. );
  314. }
  315. function testBenchmarkGetPrice() public {
  316. // Set the block timestamp to 0. As prices have < 10 timestamp and staleness
  317. // is set to 60 seconds, the getPrice should work as expected.
  318. vm.warp(0);
  319. pyth.getPrice(priceIds[0]);
  320. }
  321. function testBenchmarkGetEmaPrice() public {
  322. // Set the block timestamp to 0. As prices have < 10 timestamp and staleness
  323. // is set to 60 seconds, the getPrice should work as expected.
  324. vm.warp(0);
  325. pyth.getEmaPrice(priceIds[0]);
  326. }
  327. function testBenchmarkGetUpdateFeeWhBatch() public view {
  328. pyth.getUpdateFee(freshPricesWhBatchUpdateData);
  329. }
  330. function testBenchmarkGetUpdateFeeWhMerkle1() public view {
  331. pyth.getUpdateFee(freshPricesWhMerkleUpdateData[0]);
  332. }
  333. function testBenchmarkGetUpdateFeeWhMerkle2() public view {
  334. pyth.getUpdateFee(freshPricesWhMerkleUpdateData[1]);
  335. }
  336. function testBenchmarkGetUpdateFeeWhMerkle3() public view {
  337. pyth.getUpdateFee(freshPricesWhMerkleUpdateData[2]);
  338. }
  339. function testBenchmarkGetUpdateFeeWhMerkle4() public view {
  340. pyth.getUpdateFee(freshPricesWhMerkleUpdateData[3]);
  341. }
  342. function testBenchmarkGetUpdateFeeWhMerkle5() public view {
  343. pyth.getUpdateFee(freshPricesWhMerkleUpdateData[4]);
  344. }
  345. }