pyth-crosschain/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol

426 lines
14 KiB
Solidity

// 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;
uint64[] cachedPricesPublishTimes;
bytes[][] cachedPricesUpdateData; // i th element contains the update data for the first i prices
bytes[] allCachedPricesUpdateData; // the update data for all prices
uint[] cachedPricesUpdateFee; // i th element contains the update fee for the first i prices
uint allCachedPricesUpdateFee; // the update fee for all prices
// Fresh prices are different prices that can be used
// as a fresh price to update the prices
PythStructs.Price[] freshPrices;
uint64[] freshPricesPublishTimes;
bytes[][] freshPricesUpdateData; // i th element contains the update data for the first i prices
bytes[] allFreshPricesUpdateData; // the update data for all prices
uint[] freshPricesUpdateFee; // i th element contains the update fee for the first i prices
uint allFreshPricesUpdateFee; // the update fee for all 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
) = generateUpdateDataAndFee(cachedPrices);
cachedPricesUpdateData.push(updateData);
cachedPricesUpdateFee.push(updateFee);
(updateData, updateFee) = generateUpdateDataAndFee(freshPrices);
freshPricesUpdateData.push(updateData);
freshPricesUpdateFee.push(updateFee);
}
allCachedPricesUpdateData = cachedPricesUpdateData[NUM_PRICES - 1];
allCachedPricesUpdateFee = cachedPricesUpdateFee[NUM_PRICES - 1];
allFreshPricesUpdateData = freshPricesUpdateData[NUM_PRICES - 1];
allFreshPricesUpdateFee = freshPricesUpdateFee[NUM_PRICES - 1];
// Populate the contract with the initial prices
pyth.updatePriceFeeds{value: allCachedPricesUpdateFee}(
allCachedPricesUpdateData
);
}
function getRand() internal returns (uint val) {
++randomSeed;
val = uint(keccak256(abi.encode(randomSeed)));
}
function generateUpdateDataAndFee(
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 testBenchmarkUpdatePriceFeeds1FeedFresh() public {
pyth.updatePriceFeeds{value: freshPricesUpdateFee[0]}(
freshPricesUpdateData[0]
);
}
function testBenchmarkUpdatePriceFeeds2FeedsFresh() public {
pyth.updatePriceFeeds{value: freshPricesUpdateFee[1]}(
freshPricesUpdateData[1]
);
}
function testBenchmarkUpdatePriceFeeds3FeedsFresh() public {
pyth.updatePriceFeeds{value: freshPricesUpdateFee[2]}(
freshPricesUpdateData[2]
);
}
function testBenchmarkUpdatePriceFeeds4FeedsFresh() public {
pyth.updatePriceFeeds{value: freshPricesUpdateFee[3]}(
freshPricesUpdateData[3]
);
}
function testBenchmarkUpdatePriceFeeds5FeedsFresh() public {
pyth.updatePriceFeeds{value: freshPricesUpdateFee[4]}(
freshPricesUpdateData[4]
);
}
function testBenchmarkUpdatePriceFeeds1FeedNotFresh() public {
pyth.updatePriceFeeds{value: cachedPricesUpdateFee[0]}(
cachedPricesUpdateData[0]
);
}
function testBenchmarkUpdatePriceFeeds2FeedsNotFresh() public {
pyth.updatePriceFeeds{value: cachedPricesUpdateFee[1]}(
cachedPricesUpdateData[1]
);
}
function testBenchmarkUpdatePriceFeeds3FeedsNotFresh() public {
pyth.updatePriceFeeds{value: cachedPricesUpdateFee[2]}(
cachedPricesUpdateData[2]
);
}
function testBenchmarkUpdatePriceFeeds4FeedsNotFresh() public {
pyth.updatePriceFeeds{value: cachedPricesUpdateFee[3]}(
cachedPricesUpdateData[3]
);
}
function testBenchmarkUpdatePriceFeeds5FeedsNotFresh() public {
pyth.updatePriceFeeds{value: cachedPricesUpdateFee[4]}(
cachedPricesUpdateData[4]
);
}
function testBenchmarkUpdatePriceFeedsIfNecessaryFresh() public {
// Since the prices have advanced, the publishTimes are newer than one in
// the contract and hence, the call should succeed.
pyth.updatePriceFeedsIfNecessary{value: allFreshPricesUpdateFee}(
allFreshPricesUpdateData,
priceIds,
freshPricesPublishTimes
);
}
function testBenchmarkUpdatePriceFeedsIfNecessaryNotFresh() 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: allCachedPricesUpdateFee}(
allCachedPricesUpdateData,
priceIds,
cachedPricesPublishTimes
);
}
function testBenchmarkParsePriceFeedUpdatesForOnePriceFeed() public {
bytes32[] memory ids = new bytes32[](1);
ids[0] = priceIds[0];
pyth.parsePriceFeedUpdates{value: allFreshPricesUpdateFee}(
allFreshPricesUpdateData,
ids,
0,
50
);
}
function testBenchmarkParsePriceFeedUpdatesForTwoPriceFeed() public {
bytes32[] memory ids = new bytes32[](2);
ids[0] = priceIds[0];
ids[1] = priceIds[1];
pyth.parsePriceFeedUpdates{value: allFreshPricesUpdateFee}(
allFreshPricesUpdateData,
ids,
0,
50
);
}
function testBenchmarkParsePriceFeedUpdatesUniqueFor() public {
bytes32[] memory ids = new bytes32[](1);
ids[0] = priceIds[0];
pyth.parsePriceFeedUpdatesUnique{value: freshPricesUpdateFee[0]}(
freshPricesUpdateData[0],
ids,
uint64(freshPrices[0].publishTime),
100
);
}
function testBenchmarkParsePriceFeedUpdatesUniqueForOnePriceFeedNotWithinRange()
public
{
bytes32[] memory ids = new bytes32[](1);
ids[0] = priceIds[0];
vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector);
pyth.parsePriceFeedUpdatesUnique{value: freshPricesUpdateFee[0]}(
freshPricesUpdateData[0],
ids,
uint64(freshPrices[0].publishTime) - 1,
100
);
}
function testBenchmarkParsePriceFeedUpdates1() public {
uint numIds = 1;
bytes32[] memory ids = new bytes32[](numIds);
for (uint i = 0; i < numIds; i++) {
ids[i] = priceIds[i];
}
pyth.parsePriceFeedUpdates{value: freshPricesUpdateFee[numIds - 1]}(
freshPricesUpdateData[numIds - 1],
ids,
0,
50
);
}
function testBenchmarkParsePriceFeedUpdates2() public {
uint numIds = 2;
bytes32[] memory ids = new bytes32[](numIds);
for (uint i = 0; i < numIds; i++) {
ids[i] = priceIds[i];
}
pyth.parsePriceFeedUpdates{value: freshPricesUpdateFee[numIds - 1]}(
freshPricesUpdateData[numIds - 1],
ids,
0,
50
);
}
function testBenchmarkParsePriceFeedUpdates3() public {
uint numIds = 3;
bytes32[] memory ids = new bytes32[](numIds);
for (uint i = 0; i < numIds; i++) {
ids[i] = priceIds[i];
}
pyth.parsePriceFeedUpdates{value: freshPricesUpdateFee[numIds - 1]}(
freshPricesUpdateData[numIds - 1],
ids,
0,
50
);
}
function testBenchmarkParsePriceFeedUpdates4() public {
uint numIds = 4;
bytes32[] memory ids = new bytes32[](numIds);
for (uint i = 0; i < numIds; i++) {
ids[i] = priceIds[i];
}
pyth.parsePriceFeedUpdates{value: freshPricesUpdateFee[numIds - 1]}(
freshPricesUpdateData[numIds - 1],
ids,
0,
50
);
}
function testBenchmarkParsePriceFeedUpdates5() public {
uint numIds = 5;
bytes32[] memory ids = new bytes32[](numIds);
for (uint i = 0; i < numIds; i++) {
ids[i] = priceIds[i];
}
pyth.parsePriceFeedUpdates{value: freshPricesUpdateFee[numIds - 1]}(
freshPricesUpdateData[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: freshPricesUpdateFee[4]}( // updateFee based on number of priceFeeds in updateData
freshPricesUpdateData[4],
ids,
0,
50
);
}
function testBenchmarkParsePriceFeedUpdatesForOnePriceFeedNotWithinRange()
public
{
bytes32[] memory ids = new bytes32[](1);
ids[0] = priceIds[0];
vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector);
pyth.parsePriceFeedUpdates{value: freshPricesUpdateFee[0]}(
freshPricesUpdateData[0],
ids,
50,
100
);
}
function testBenchmarkParsePriceFeedUpdatesForAllPriceFeedsNotWithinRange()
public
{
bytes32[] memory ids = new bytes32[](1);
ids[0] = priceIds[0];
vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector);
pyth.parsePriceFeedUpdates{value: allFreshPricesUpdateFee}(
allFreshPricesUpdateData,
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 testBenchmarkGetUpdateFee1() public view {
pyth.getUpdateFee(freshPricesUpdateData[0]);
}
function testBenchmarkGetUpdateFee2() public view {
pyth.getUpdateFee(freshPricesUpdateData[1]);
}
function testBenchmarkGetUpdateFee3() public view {
pyth.getUpdateFee(freshPricesUpdateData[2]);
}
function testBenchmarkGetUpdateFee4() public view {
pyth.getUpdateFee(freshPricesUpdateData[3]);
}
function testBenchmarkGetUpdateFee5() public view {
pyth.getUpdateFee(freshPricesUpdateData[4]);
}
}