// SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.0; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "forge-std/Test.sol"; import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol"; import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; import "./utils/WormholeTestUtils.t.sol"; import "./utils/PythTestUtils.t.sol"; contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils { // 19, current mainnet number of guardians, is used to have gas estimates // close to our mainnet transactions. uint8 constant NUM_GUARDIANS = 19; // 2/3 of the guardians should sign a message for a VAA which is 13 out of 19 guardians. // It is possible to have more signers but the median seems to be 13. uint8 constant NUM_GUARDIAN_SIGNERS = 13; // We use 5 prices to form a batch of 5 prices, close to our mainnet transactions. uint8 constant NUM_PRICES = 5; // We will have less than 512 price for a foreseeable future. uint8 constant MERKLE_TREE_DEPTH = 9; IWormhole public wormhole; IPyth public pyth; bytes32[] priceIds; // Cached prices are populated in the setUp PythStructs.Price[] cachedPrices; bytes[] cachedPricesWhBatchUpdateData; uint cachedPricesWhBatchUpdateFee; uint64[] cachedPricesPublishTimes; bytes[][] cachedPricesWhMerkleUpdateData; // i th element contains the update data for the first i prices uint[] cachedPricesWhMerkleUpdateFee; // i th element contains the update fee for the first i prices // Fresh prices are different prices that can be used // as a fresh price to update the prices PythStructs.Price[] freshPrices; bytes[] freshPricesWhBatchUpdateData; uint freshPricesWhBatchUpdateFee; uint64[] freshPricesPublishTimes; bytes[][] freshPricesWhMerkleUpdateData; // i th element contains the update data for the first i prices uint[] freshPricesWhMerkleUpdateFee; // i th element contains the update fee for the first i prices uint64 sequence; uint randomSeed; function setUp() public { address wormholeAddr = setUpWormholeReceiver(NUM_GUARDIANS); wormhole = IWormhole(wormholeAddr); pyth = IPyth(setUpPyth(wormholeAddr)); priceIds = new bytes32[](NUM_PRICES); priceIds[0] = bytes32( 0x1000000000000000000000000000000000000000000000000000000000000f00 ); for (uint i = 1; i < NUM_PRICES; ++i) { priceIds[i] = bytes32(uint256(priceIds[i - 1]) + 1); } for (uint i = 0; i < NUM_PRICES; ++i) { uint64 publishTime = uint64(getRand() % 10) + 1; // to make sure prevPublishTime is >= 0 cachedPrices.push( PythStructs.Price( int64(uint64(getRand() % 1000)), // Price uint64(getRand() % 100), // Confidence -5, // Expo publishTime ) ); cachedPricesPublishTimes.push(publishTime); publishTime += uint64(getRand() % 10); freshPrices.push( PythStructs.Price( int64(uint64(getRand() % 1000)), // Price uint64(getRand() % 100), // Confidence -5, // Expo publishTime ) ); freshPricesPublishTimes.push(publishTime); // Generate Wormhole Merkle update data and fee for the first i th prices ( bytes[] memory updateData, uint updateFee ) = generateWhMerkleUpdateDataAndFee(cachedPrices); cachedPricesWhMerkleUpdateData.push(updateData); cachedPricesWhMerkleUpdateFee.push(updateFee); (updateData, updateFee) = generateWhMerkleUpdateDataAndFee( freshPrices ); freshPricesWhMerkleUpdateData.push(updateData); freshPricesWhMerkleUpdateFee.push(updateFee); } // Populate the contract with the initial prices ( cachedPricesWhBatchUpdateData, cachedPricesWhBatchUpdateFee ) = generateWhBatchUpdateDataAndFee(cachedPrices); pyth.updatePriceFeeds{value: cachedPricesWhBatchUpdateFee}( cachedPricesWhBatchUpdateData ); ( freshPricesWhBatchUpdateData, freshPricesWhBatchUpdateFee ) = generateWhBatchUpdateDataAndFee(freshPrices); } function getRand() internal returns (uint val) { ++randomSeed; val = uint(keccak256(abi.encode(randomSeed))); } function generateWhBatchUpdateDataAndFee( PythStructs.Price[] memory prices ) internal returns (bytes[] memory updateData, uint updateFee) { bytes memory vaa = generateWhBatchUpdate( pricesToPriceAttestations(priceIds, prices), sequence, NUM_GUARDIAN_SIGNERS ); ++sequence; updateData = new bytes[](1); updateData[0] = vaa; updateFee = pyth.getUpdateFee(updateData); } function generateWhMerkleUpdateDataAndFee( PythStructs.Price[] memory prices ) internal returns (bytes[] memory updateData, uint updateFee) { updateData = new bytes[](1); updateData[0] = generateWhMerkleUpdate( pricesToPriceFeedMessages(priceIds, prices), MERKLE_TREE_DEPTH, NUM_GUARDIAN_SIGNERS ); updateFee = pyth.getUpdateFee(updateData); } function testBenchmarkUpdatePriceFeedsWhBatchFresh() public { pyth.updatePriceFeeds{value: freshPricesWhBatchUpdateFee}( freshPricesWhBatchUpdateData ); } function testBenchmarkUpdatePriceFeedsWhBatchNotFresh() public { pyth.updatePriceFeeds{value: cachedPricesWhBatchUpdateFee}( cachedPricesWhBatchUpdateData ); } function testBenchmarkUpdatePriceFeedsWhMerkle1FeedFresh() public { pyth.updatePriceFeeds{value: freshPricesWhMerkleUpdateFee[0]}( freshPricesWhMerkleUpdateData[0] ); } function testBenchmarkUpdatePriceFeedsWhMerkle2FeedsFresh() public { pyth.updatePriceFeeds{value: freshPricesWhMerkleUpdateFee[1]}( freshPricesWhMerkleUpdateData[1] ); } function testBenchmarkUpdatePriceFeedsWhMerkle3FeedsFresh() public { pyth.updatePriceFeeds{value: freshPricesWhMerkleUpdateFee[2]}( freshPricesWhMerkleUpdateData[2] ); } function testBenchmarkUpdatePriceFeedsWhMerkle4FeedsFresh() public { pyth.updatePriceFeeds{value: freshPricesWhMerkleUpdateFee[3]}( freshPricesWhMerkleUpdateData[3] ); } function testBenchmarkUpdatePriceFeedsWhMerkle5FeedsFresh() public { pyth.updatePriceFeeds{value: freshPricesWhMerkleUpdateFee[4]}( freshPricesWhMerkleUpdateData[4] ); } function testBenchmarkUpdatePriceFeedsWhMerkle1FeedNotFresh() public { pyth.updatePriceFeeds{value: cachedPricesWhMerkleUpdateFee[0]}( cachedPricesWhMerkleUpdateData[0] ); } function testBenchmarkUpdatePriceFeedsWhMerkle2FeedsNotFresh() public { pyth.updatePriceFeeds{value: cachedPricesWhMerkleUpdateFee[1]}( cachedPricesWhMerkleUpdateData[1] ); } function testBenchmarkUpdatePriceFeedsWhMerkle3FeedsNotFresh() public { pyth.updatePriceFeeds{value: cachedPricesWhMerkleUpdateFee[2]}( cachedPricesWhMerkleUpdateData[2] ); } function testBenchmarkUpdatePriceFeedsWhMerkle4FeedsNotFresh() public { pyth.updatePriceFeeds{value: cachedPricesWhMerkleUpdateFee[3]}( cachedPricesWhMerkleUpdateData[3] ); } function testBenchmarkUpdatePriceFeedsWhMerkle5FeedsNotFresh() public { pyth.updatePriceFeeds{value: cachedPricesWhMerkleUpdateFee[4]}( cachedPricesWhMerkleUpdateData[4] ); } function testBenchmarkUpdatePriceFeedsIfNecessaryWhBatchFresh() public { // Since the prices have advanced, the publishTimes are newer than one in // the contract and hence, the call should succeed. pyth.updatePriceFeedsIfNecessary{value: freshPricesWhBatchUpdateFee}( freshPricesWhBatchUpdateData, priceIds, freshPricesPublishTimes ); } function testBenchmarkUpdatePriceFeedsIfNecessaryWhBatchNotFresh() public { // Since the price is not advanced, the publishTimes are the same as the // ones in the contract. vm.expectRevert(PythErrors.NoFreshUpdate.selector); pyth.updatePriceFeedsIfNecessary{value: cachedPricesWhBatchUpdateFee}( cachedPricesWhBatchUpdateData, priceIds, cachedPricesPublishTimes ); } function testBenchmarkParsePriceFeedUpdatesForOnePriceFeed() public { bytes32[] memory ids = new bytes32[](1); ids[0] = priceIds[0]; pyth.parsePriceFeedUpdates{value: freshPricesWhBatchUpdateFee}( freshPricesWhBatchUpdateData, ids, 0, 50 ); } function testBenchmarkParsePriceFeedUpdatesForTwoPriceFeed() public { bytes32[] memory ids = new bytes32[](2); ids[0] = priceIds[0]; ids[1] = priceIds[1]; pyth.parsePriceFeedUpdates{value: freshPricesWhBatchUpdateFee}( freshPricesWhBatchUpdateData, ids, 0, 50 ); } function testBenchmarkParsePriceFeedUpdatesUniqueForWhMerkle() public { bytes32[] memory ids = new bytes32[](1); ids[0] = priceIds[0]; pyth.parsePriceFeedUpdatesUnique{ value: freshPricesWhMerkleUpdateFee[0] }( freshPricesWhMerkleUpdateData[0], ids, uint64(freshPrices[0].publishTime), 100 ); } function testBenchmarkParsePriceFeedUpdatesUniqueWhMerkleForOnePriceFeedNotWithinRange() public { bytes32[] memory ids = new bytes32[](1); ids[0] = priceIds[0]; vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector); pyth.parsePriceFeedUpdatesUnique{ value: freshPricesWhMerkleUpdateFee[0] }( freshPricesWhMerkleUpdateData[0], ids, uint64(freshPrices[0].publishTime) - 1, 100 ); } function testBenchmarkParsePriceFeedUpdatesForWhMerkle1() public { uint numIds = 1; bytes32[] memory ids = new bytes32[](numIds); for (uint i = 0; i < numIds; i++) { ids[i] = priceIds[i]; } pyth.parsePriceFeedUpdates{ value: freshPricesWhMerkleUpdateFee[numIds - 1] }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50); } function testBenchmarkParsePriceFeedUpdatesForWhMerkle2() public { uint numIds = 2; bytes32[] memory ids = new bytes32[](numIds); for (uint i = 0; i < numIds; i++) { ids[i] = priceIds[i]; } pyth.parsePriceFeedUpdates{ value: freshPricesWhMerkleUpdateFee[numIds - 1] }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50); } function testBenchmarkParsePriceFeedUpdatesForWhMerkle3() public { uint numIds = 3; bytes32[] memory ids = new bytes32[](numIds); for (uint i = 0; i < numIds; i++) { ids[i] = priceIds[i]; } pyth.parsePriceFeedUpdates{ value: freshPricesWhMerkleUpdateFee[numIds - 1] }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50); } function testBenchmarkParsePriceFeedUpdatesForWhMerkle4() public { uint numIds = 4; bytes32[] memory ids = new bytes32[](numIds); for (uint i = 0; i < numIds; i++) { ids[i] = priceIds[i]; } pyth.parsePriceFeedUpdates{ value: freshPricesWhMerkleUpdateFee[numIds - 1] }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50); } function testBenchmarkParsePriceFeedUpdatesForWhMerkle5() public { uint numIds = 5; bytes32[] memory ids = new bytes32[](numIds); for (uint i = 0; i < numIds; i++) { ids[i] = priceIds[i]; } pyth.parsePriceFeedUpdates{ value: freshPricesWhMerkleUpdateFee[numIds - 1] }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50); } function testBenchmarkParsePriceFeedUpdatesForAllPriceFeedsShuffledSubsetPriceIds() public { uint numIds = 3; bytes32[] memory ids = new bytes32[](numIds); ids[0] = priceIds[4]; ids[1] = priceIds[2]; ids[2] = priceIds[0]; pyth.parsePriceFeedUpdates{value: freshPricesWhMerkleUpdateFee[4]}( // updateFee based on number of priceFeeds in updateData freshPricesWhMerkleUpdateData[4], ids, 0, 50 ); } function testBenchmarkParsePriceFeedUpdatesWhMerkleForOnePriceFeedNotWithinRange() public { bytes32[] memory ids = new bytes32[](1); ids[0] = priceIds[0]; vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector); pyth.parsePriceFeedUpdates{value: freshPricesWhMerkleUpdateFee[0]}( freshPricesWhMerkleUpdateData[0], ids, 50, 100 ); } function testBenchmarkParsePriceFeedUpdatesForOnePriceFeedNotWithinRange() public { bytes32[] memory ids = new bytes32[](1); ids[0] = priceIds[0]; vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector); pyth.parsePriceFeedUpdates{value: freshPricesWhBatchUpdateFee}( freshPricesWhBatchUpdateData, ids, 50, 100 ); } function testBenchmarkGetPrice() public { // Set the block timestamp to 0. As prices have < 10 timestamp and staleness // is set to 60 seconds, the getPrice should work as expected. vm.warp(0); pyth.getPrice(priceIds[0]); } function testBenchmarkGetEmaPrice() public { // Set the block timestamp to 0. As prices have < 10 timestamp and staleness // is set to 60 seconds, the getPrice should work as expected. vm.warp(0); pyth.getEmaPrice(priceIds[0]); } function testBenchmarkGetUpdateFeeWhBatch() public view { pyth.getUpdateFee(freshPricesWhBatchUpdateData); } function testBenchmarkGetUpdateFeeWhMerkle1() public view { pyth.getUpdateFee(freshPricesWhMerkleUpdateData[0]); } function testBenchmarkGetUpdateFeeWhMerkle2() public view { pyth.getUpdateFee(freshPricesWhMerkleUpdateData[1]); } function testBenchmarkGetUpdateFeeWhMerkle3() public view { pyth.getUpdateFee(freshPricesWhMerkleUpdateData[2]); } function testBenchmarkGetUpdateFeeWhMerkle4() public view { pyth.getUpdateFee(freshPricesWhMerkleUpdateData[3]); } function testBenchmarkGetUpdateFeeWhMerkle5() public view { pyth.getUpdateFee(freshPricesWhMerkleUpdateData[4]); } function testBenchmarkWormholeParseAndVerifyVMBatchAttestation() public { wormhole.parseAndVerifyVM(freshPricesWhBatchUpdateData[0]); } }