|
|
@@ -22,6 +22,9 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
{
|
|
|
IPyth public pyth;
|
|
|
|
|
|
+ // -1 is equal to 0xffffff which is the biggest uint if converted back
|
|
|
+ uint64 constant MAX_UINT64 = uint64(int64(-1));
|
|
|
+
|
|
|
function setUp() public {
|
|
|
pyth = IPyth(setUpPyth(setUpWormhole(1)));
|
|
|
}
|
|
|
@@ -46,6 +49,42 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
assertEq(emaPrice.publishTime, priceFeedMessage.publishTime);
|
|
|
}
|
|
|
|
|
|
+ function assertParsedPriceFeedEqualsMessage(
|
|
|
+ PythStructs.PriceFeed memory priceFeed,
|
|
|
+ PriceFeedMessage memory priceFeedMessage,
|
|
|
+ bytes32 priceId
|
|
|
+ ) internal {
|
|
|
+ assertEq(priceFeed.id, priceId);
|
|
|
+ assertEq(priceFeed.price.price, priceFeedMessage.price);
|
|
|
+ assertEq(priceFeed.price.conf, priceFeedMessage.conf);
|
|
|
+ assertEq(priceFeed.price.expo, priceFeedMessage.expo);
|
|
|
+ assertEq(priceFeed.price.publishTime, priceFeedMessage.publishTime);
|
|
|
+ assertEq(priceFeed.emaPrice.price, priceFeedMessage.emaPrice);
|
|
|
+ assertEq(priceFeed.emaPrice.conf, priceFeedMessage.emaConf);
|
|
|
+ assertEq(priceFeed.emaPrice.expo, priceFeedMessage.expo);
|
|
|
+ assertEq(priceFeed.emaPrice.publishTime, priceFeedMessage.publishTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ function assertParsedPriceFeedStored(
|
|
|
+ PythStructs.PriceFeed memory priceFeed
|
|
|
+ ) internal {
|
|
|
+ PythStructs.Price memory aggregatePrice = pyth.getPriceUnsafe(
|
|
|
+ priceFeed.id
|
|
|
+ );
|
|
|
+ assertEq(aggregatePrice.price, priceFeed.price.price);
|
|
|
+ assertEq(aggregatePrice.conf, priceFeed.price.conf);
|
|
|
+ assertEq(aggregatePrice.expo, priceFeed.price.expo);
|
|
|
+ assertEq(aggregatePrice.publishTime, priceFeed.price.publishTime);
|
|
|
+
|
|
|
+ PythStructs.Price memory emaPrice = pyth.getEmaPriceUnsafe(
|
|
|
+ priceFeed.id
|
|
|
+ );
|
|
|
+ assertEq(emaPrice.price, priceFeed.emaPrice.price);
|
|
|
+ assertEq(emaPrice.conf, priceFeed.emaPrice.conf);
|
|
|
+ assertEq(emaPrice.expo, priceFeed.emaPrice.expo);
|
|
|
+ assertEq(emaPrice.publishTime, priceFeed.emaPrice.publishTime);
|
|
|
+ }
|
|
|
+
|
|
|
function generateRandomPriceFeedMessage(
|
|
|
uint numPriceFeeds
|
|
|
) internal returns (PriceFeedMessage[] memory priceFeedMessages) {
|
|
|
@@ -162,11 +201,62 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
|
|
|
uint updateFee = pyth.getUpdateFee(updateData);
|
|
|
|
|
|
+ bytes32[] memory priceIds = new bytes32[](3);
|
|
|
+ priceIds[0] = priceFeedMessages1[0].priceId;
|
|
|
+ priceIds[1] = priceFeedMessages1[1].priceId;
|
|
|
+ priceIds[2] = priceFeedMessages2[0].priceId;
|
|
|
+ // parse price feeds before updating since parsing price feeds should be independent
|
|
|
+ // of whatever is currently stored in the contract.
|
|
|
+ PythStructs.PriceFeed[] memory priceFeeds = pyth.parsePriceFeedUpdates{
|
|
|
+ value: updateFee
|
|
|
+ }(updateData, priceIds, 0, MAX_UINT64);
|
|
|
+
|
|
|
+ PriceFeedMessage[]
|
|
|
+ memory expectedPriceFeedMessages = new PriceFeedMessage[](3);
|
|
|
+ // Only the first occurrence of a valid priceFeedMessage for a paritcular priceFeed.id
|
|
|
+ // within an updateData will be parsed which is why we exclude priceFeedMessages2[1]
|
|
|
+ // since it has the same priceFeed.id as priceFeedMessages1[0] even though it has a later publishTime.
|
|
|
+ // This is different than how updatePriceFeed behaves which will always update using the data
|
|
|
+ // of the priceFeedMessage with the latest publishTime for a particular priceFeed.id
|
|
|
+ expectedPriceFeedMessages[0] = priceFeedMessages1[0];
|
|
|
+ expectedPriceFeedMessages[1] = priceFeedMessages1[1];
|
|
|
+ expectedPriceFeedMessages[2] = priceFeedMessages2[0];
|
|
|
+ for (uint i = 0; i < expectedPriceFeedMessages.length; i++) {
|
|
|
+ assertParsedPriceFeedEqualsMessage(
|
|
|
+ priceFeeds[i],
|
|
|
+ expectedPriceFeedMessages[i],
|
|
|
+ priceIds[i]
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // parse updateData[1] for priceFeedMessages1[0].priceId since this has the latest publishTime
|
|
|
+ // for that priceId and should be the one that is stored.
|
|
|
+ bytes32[] memory priceIds1 = new bytes32[](1);
|
|
|
+ priceIds1[0] = priceFeedMessages1[0].priceId;
|
|
|
+ bytes[] memory parseUpdateDataInput1 = new bytes[](1);
|
|
|
+ parseUpdateDataInput1[0] = updateData[1];
|
|
|
+
|
|
|
+ PythStructs.PriceFeed[] memory priceFeeds1 = pyth.parsePriceFeedUpdates{
|
|
|
+ value: updateFee
|
|
|
+ }(parseUpdateDataInput1, priceIds1, 0, MAX_UINT64);
|
|
|
+
|
|
|
pyth.updatePriceFeeds{value: updateFee}(updateData);
|
|
|
|
|
|
+ // check stored price feed information matches updateData
|
|
|
assertPriceFeedMessageStored(priceFeedMessages1[1]);
|
|
|
assertPriceFeedMessageStored(priceFeedMessages2[0]);
|
|
|
assertPriceFeedMessageStored(priceFeedMessages2[1]);
|
|
|
+
|
|
|
+ PythStructs.PriceFeed[]
|
|
|
+ memory expectedPriceFeeds = new PythStructs.PriceFeed[](3);
|
|
|
+ expectedPriceFeeds[0] = priceFeeds1[0];
|
|
|
+ expectedPriceFeeds[1] = priceFeeds[1];
|
|
|
+ expectedPriceFeeds[2] = priceFeeds[2];
|
|
|
+
|
|
|
+ // check stored price feed information matches parsed price feeds
|
|
|
+ for (uint i = 0; i < expectedPriceFeeds.length; i++) {
|
|
|
+ assertParsedPriceFeedStored(expectedPriceFeeds[i]);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
function testUpdatePriceFeedWithWormholeMerkleIgnoresOutOfOrderUpdateSingleCall()
|
|
|
@@ -205,6 +295,28 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
pyth.updatePriceFeeds{value: updateFee}(updateData);
|
|
|
|
|
|
assertPriceFeedMessageStored(priceFeedMessages1[0]);
|
|
|
+
|
|
|
+ bytes32[] memory priceIds = new bytes32[](1);
|
|
|
+ priceIds[0] = priceFeedMessages1[0].priceId;
|
|
|
+
|
|
|
+ PythStructs.PriceFeed[] memory priceFeeds = pyth.parsePriceFeedUpdates{
|
|
|
+ value: updateFee
|
|
|
+ }(updateData, priceIds, 0, MAX_UINT64);
|
|
|
+ assertEq(priceFeeds.length, 1);
|
|
|
+ assertParsedPriceFeedStored(priceFeeds[0]);
|
|
|
+
|
|
|
+ // parsePriceFeedUpdates should return the first priceFeed in the case
|
|
|
+ // that the updateData contains multiple feeds with the same id.
|
|
|
+ // Swap the order of updates in updateData to verify that the other priceFeed is returned
|
|
|
+ bytes[] memory updateData1 = new bytes[](2);
|
|
|
+ updateData1[0] = updateData[1];
|
|
|
+ updateData1[1] = updateData[0];
|
|
|
+
|
|
|
+ PythStructs.PriceFeed[] memory priceFeeds1 = pyth.parsePriceFeedUpdates{
|
|
|
+ value: updateFee
|
|
|
+ }(updateData1, priceIds, 0, MAX_UINT64);
|
|
|
+ assertEq(priceFeeds1.length, 1);
|
|
|
+ assertEq(priceFeeds1[0].price.publishTime, 5);
|
|
|
}
|
|
|
|
|
|
function testUpdatePriceFeedWithWormholeMerkleIgnoresOutOfOrderUpdateMultiCall()
|
|
|
@@ -227,7 +339,6 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
uint updateFee
|
|
|
) = createWormholeMerkleUpdateData(priceFeedMessages1);
|
|
|
pyth.updatePriceFeeds{value: updateFee}(updateData);
|
|
|
- assertPriceFeedMessageStored(priceFeedMessages1[0]);
|
|
|
|
|
|
(updateData, updateFee) = createWormholeMerkleUpdateData(
|
|
|
priceFeedMessages2
|
|
|
@@ -237,6 +348,86 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
assertPriceFeedMessageStored(priceFeedMessages1[0]);
|
|
|
}
|
|
|
|
|
|
+ function testParsePriceFeedUpdatesWithWormholeMerklWorksWithOurOfOrderUpdateMultiCall()
|
|
|
+ public
|
|
|
+ {
|
|
|
+ PriceFeedMessage[]
|
|
|
+ memory priceFeedMessages1 = generateRandomPriceFeedMessage(1);
|
|
|
+ PriceFeedMessage[]
|
|
|
+ memory priceFeedMessages2 = generateRandomPriceFeedMessage(1);
|
|
|
+
|
|
|
+ // Make the price ids the same
|
|
|
+ priceFeedMessages2[0].priceId = priceFeedMessages1[0].priceId;
|
|
|
+ // Adjust the timestamps so the second timestamp is smaller than the first
|
|
|
+ // Parse should work regardless of what's stored on chain.
|
|
|
+ priceFeedMessages1[0].publishTime = 10;
|
|
|
+ priceFeedMessages2[0].publishTime = 5;
|
|
|
+
|
|
|
+ (
|
|
|
+ bytes[] memory updateData,
|
|
|
+ uint updateFee
|
|
|
+ ) = createWormholeMerkleUpdateData(priceFeedMessages1);
|
|
|
+ bytes32[] memory priceIds = new bytes32[](1);
|
|
|
+ priceIds[0] = priceFeedMessages1[0].priceId;
|
|
|
+ PythStructs.PriceFeed[] memory priceFeeds = pyth.parsePriceFeedUpdates{
|
|
|
+ value: updateFee
|
|
|
+ }(updateData, priceIds, 0, MAX_UINT64);
|
|
|
+
|
|
|
+ // Parse should always return the same value regardless of what's stored on chain.
|
|
|
+ assertEq(priceFeeds.length, 1);
|
|
|
+ assertParsedPriceFeedEqualsMessage(
|
|
|
+ priceFeeds[0],
|
|
|
+ priceFeedMessages1[0],
|
|
|
+ priceIds[0]
|
|
|
+ );
|
|
|
+ pyth.updatePriceFeeds{value: updateFee}(updateData);
|
|
|
+ priceFeeds = pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
+ assertEq(priceFeeds.length, 1);
|
|
|
+ assertParsedPriceFeedEqualsMessage(
|
|
|
+ priceFeeds[0],
|
|
|
+ priceFeedMessages1[0],
|
|
|
+ priceIds[0]
|
|
|
+ );
|
|
|
+
|
|
|
+ (
|
|
|
+ bytes[] memory updateData1,
|
|
|
+ uint updateFee1
|
|
|
+ ) = createWormholeMerkleUpdateData(priceFeedMessages2);
|
|
|
+ pyth.updatePriceFeeds{value: updateFee1}(updateData1);
|
|
|
+ // reparse the original updateData should still return the same thing
|
|
|
+ priceFeeds = pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
+ assertEq(priceFeeds.length, 1);
|
|
|
+ assertParsedPriceFeedEqualsMessage(
|
|
|
+ priceFeeds[0],
|
|
|
+ priceFeedMessages1[0],
|
|
|
+ priceIds[0]
|
|
|
+ );
|
|
|
+
|
|
|
+ // parsing the second message should return the data based on the second messagef
|
|
|
+ priceFeeds = pyth.parsePriceFeedUpdates{value: updateFee1}(
|
|
|
+ updateData1,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
+ assertEq(priceFeeds.length, 1);
|
|
|
+ assertParsedPriceFeedEqualsMessage(
|
|
|
+ priceFeeds[0],
|
|
|
+ priceFeedMessages2[0],
|
|
|
+ priceIds[0]
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
function isNotMatch(
|
|
|
bytes memory a,
|
|
|
bytes memory b
|
|
|
@@ -249,12 +440,23 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
/// expected value, that item will be forged to be invalid.
|
|
|
function createAndForgeWormholeMerkleUpdateData(
|
|
|
bytes memory forgeItem
|
|
|
- ) public returns (bytes[] memory updateData, uint updateFee) {
|
|
|
+ )
|
|
|
+ public
|
|
|
+ returns (
|
|
|
+ bytes[] memory updateData,
|
|
|
+ uint updateFee,
|
|
|
+ bytes32[] memory priceIds
|
|
|
+ )
|
|
|
+ {
|
|
|
uint numPriceFeeds = 10;
|
|
|
PriceFeedMessage[]
|
|
|
memory priceFeedMessages = generateRandomPriceFeedMessage(
|
|
|
numPriceFeeds
|
|
|
);
|
|
|
+ priceIds = new bytes32[](numPriceFeeds);
|
|
|
+ for (uint i = 0; i < numPriceFeeds; i++) {
|
|
|
+ priceIds[i] = priceFeedMessages[i].priceId;
|
|
|
+ }
|
|
|
|
|
|
bytes[] memory encodedPriceFeedMessages = encodePriceFeedMessages(
|
|
|
priceFeedMessages
|
|
|
@@ -327,11 +529,21 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
// In this test the Wormhole accumulator magic is wrong and the update gets reverted.
|
|
|
(
|
|
|
bytes[] memory updateData,
|
|
|
- uint updateFee
|
|
|
+ uint updateFee,
|
|
|
+ bytes32[] memory priceIds
|
|
|
) = createAndForgeWormholeMerkleUpdateData("whMagic");
|
|
|
|
|
|
vm.expectRevert(PythErrors.InvalidUpdateData.selector);
|
|
|
pyth.updatePriceFeeds{value: updateFee}(updateData);
|
|
|
+
|
|
|
+ vm.expectRevert(PythErrors.InvalidUpdateData.selector);
|
|
|
+
|
|
|
+ pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
function testUpdatePriceFeedWithWormholeMerkleRevertsOnWrongVAAPayloadUpdateType()
|
|
|
@@ -342,11 +554,20 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
|
|
|
(
|
|
|
bytes[] memory updateData,
|
|
|
- uint updateFee
|
|
|
+ uint updateFee,
|
|
|
+ bytes32[] memory priceIds
|
|
|
) = createAndForgeWormholeMerkleUpdateData("whUpdateType");
|
|
|
vm.expectRevert(); // Reason: Conversion into non-existent enum type. However it
|
|
|
// was not possible to check the revert reason in the test.
|
|
|
pyth.updatePriceFeeds{value: updateFee}(updateData);
|
|
|
+
|
|
|
+ vm.expectRevert();
|
|
|
+ pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
function testUpdatePriceFeedWithWormholeMerkleRevertsOnWrongVAASource()
|
|
|
@@ -355,15 +576,34 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
// In this test the Wormhole message source is wrong.
|
|
|
(
|
|
|
bytes[] memory updateData,
|
|
|
- uint updateFee
|
|
|
+ uint updateFee,
|
|
|
+ bytes32[] memory priceIds
|
|
|
) = createAndForgeWormholeMerkleUpdateData("whSourceAddress");
|
|
|
vm.expectRevert(PythErrors.InvalidUpdateDataSource.selector);
|
|
|
pyth.updatePriceFeeds{value: updateFee}(updateData);
|
|
|
- (updateData, updateFee) = createAndForgeWormholeMerkleUpdateData(
|
|
|
- "whSourceChain"
|
|
|
+ vm.expectRevert(PythErrors.InvalidUpdateDataSource.selector);
|
|
|
+ pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
);
|
|
|
+
|
|
|
+ (
|
|
|
+ updateData,
|
|
|
+ updateFee,
|
|
|
+ priceIds
|
|
|
+ ) = createAndForgeWormholeMerkleUpdateData("whSourceChain");
|
|
|
vm.expectRevert(PythErrors.InvalidUpdateDataSource.selector);
|
|
|
pyth.updatePriceFeeds{value: updateFee}(updateData);
|
|
|
+
|
|
|
+ vm.expectRevert(PythErrors.InvalidUpdateDataSource.selector);
|
|
|
+ pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
function testUpdatePriceFeedWithWormholeMerkleRevertsOnWrongRootDigest()
|
|
|
@@ -372,10 +612,19 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
// In this test the Wormhole merkle proof digest is wrong
|
|
|
(
|
|
|
bytes[] memory updateData,
|
|
|
- uint updateFee
|
|
|
+ uint updateFee,
|
|
|
+ bytes32[] memory priceIds
|
|
|
) = createAndForgeWormholeMerkleUpdateData("rootDigest");
|
|
|
vm.expectRevert(PythErrors.InvalidUpdateData.selector);
|
|
|
pyth.updatePriceFeeds{value: updateFee}(updateData);
|
|
|
+
|
|
|
+ vm.expectRevert(PythErrors.InvalidUpdateData.selector);
|
|
|
+ pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
function testUpdatePriceFeedWithWormholeMerkleRevertsOnWrongProofItem()
|
|
|
@@ -384,10 +633,19 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
// In this test all Wormhole merkle proof items are the first item proof
|
|
|
(
|
|
|
bytes[] memory updateData,
|
|
|
- uint updateFee
|
|
|
+ uint updateFee,
|
|
|
+ bytes32[] memory priceIds
|
|
|
) = createAndForgeWormholeMerkleUpdateData("proofItem");
|
|
|
vm.expectRevert(PythErrors.InvalidUpdateData.selector);
|
|
|
pyth.updatePriceFeeds{value: updateFee}(updateData);
|
|
|
+
|
|
|
+ vm.expectRevert(PythErrors.InvalidUpdateData.selector);
|
|
|
+ pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
function testUpdatePriceFeedWithWormholeMerkleRevertsOnWrongHeader()
|
|
|
@@ -396,17 +654,35 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
// In this test the message headers are wrong
|
|
|
(
|
|
|
bytes[] memory updateData,
|
|
|
- uint updateFee
|
|
|
+ uint updateFee,
|
|
|
+ bytes32[] memory priceIds
|
|
|
) = createAndForgeWormholeMerkleUpdateData("headerMagic");
|
|
|
vm.expectRevert(); // The revert reason is not deterministic because when it doesn't match it goes through
|
|
|
// the old approach.
|
|
|
pyth.updatePriceFeeds{value: updateFee}(updateData);
|
|
|
-
|
|
|
- (updateData, updateFee) = createAndForgeWormholeMerkleUpdateData(
|
|
|
- "headerMajorVersion"
|
|
|
+ vm.expectRevert();
|
|
|
+ pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
);
|
|
|
+
|
|
|
+ (
|
|
|
+ updateData,
|
|
|
+ updateFee,
|
|
|
+ priceIds
|
|
|
+ ) = createAndForgeWormholeMerkleUpdateData("headerMajorVersion");
|
|
|
vm.expectRevert(PythErrors.InvalidUpdateData.selector);
|
|
|
pyth.updatePriceFeeds{value: updateFee}(updateData);
|
|
|
+
|
|
|
+ vm.expectRevert(PythErrors.InvalidUpdateData.selector);
|
|
|
+ pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
function testUpdatePriceFeedWithWormholeMerkleRevertsIfUpdateFeeIsNotPaid()
|
|
|
@@ -421,7 +697,222 @@ contract PythWormholeMerkleAccumulatorTest is
|
|
|
priceFeedMessages
|
|
|
);
|
|
|
|
|
|
+ bytes32[] memory priceIds = new bytes32[](numPriceFeeds);
|
|
|
+ for (uint i = 0; i < numPriceFeeds; i++) {
|
|
|
+ priceIds[i] = priceFeedMessages[i].priceId;
|
|
|
+ }
|
|
|
vm.expectRevert(PythErrors.InsufficientFee.selector);
|
|
|
pyth.updatePriceFeeds{value: 0}(updateData);
|
|
|
+
|
|
|
+ vm.expectRevert(PythErrors.InsufficientFee.selector);
|
|
|
+ pyth.parsePriceFeedUpdates{value: 0}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ function testParsePriceFeedWithWormholeMerkleWorks(uint seed) public {
|
|
|
+ setRandSeed(seed);
|
|
|
+
|
|
|
+ uint numPriceFeeds = (getRandUint() % 10) + 1;
|
|
|
+ PriceFeedMessage[]
|
|
|
+ memory priceFeedMessages = generateRandomPriceFeedMessage(
|
|
|
+ numPriceFeeds
|
|
|
+ );
|
|
|
+ (
|
|
|
+ bytes[] memory updateData,
|
|
|
+ uint updateFee
|
|
|
+ ) = createWormholeMerkleUpdateData(priceFeedMessages);
|
|
|
+ bytes32[] memory priceIds = new bytes32[](numPriceFeeds);
|
|
|
+ for (uint i = 0; i < numPriceFeeds; i++) {
|
|
|
+ priceIds[i] = priceFeedMessages[i].priceId;
|
|
|
+ }
|
|
|
+ PythStructs.PriceFeed[] memory priceFeeds = pyth.parsePriceFeedUpdates{
|
|
|
+ value: updateFee
|
|
|
+ }(updateData, priceIds, 0, MAX_UINT64);
|
|
|
+
|
|
|
+ for (uint i = 0; i < priceFeeds.length; i++) {
|
|
|
+ assertParsedPriceFeedEqualsMessage(
|
|
|
+ priceFeeds[i],
|
|
|
+ priceFeedMessages[i],
|
|
|
+ priceIds[i]
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // update priceFeedMessages
|
|
|
+ for (uint i = 0; i < numPriceFeeds; i++) {
|
|
|
+ priceFeedMessages[i].price = getRandInt64();
|
|
|
+ priceFeedMessages[i].conf = getRandUint64();
|
|
|
+ priceFeedMessages[i].expo = getRandInt32();
|
|
|
+ priceFeedMessages[i].publishTime = getRandUint64();
|
|
|
+ priceFeedMessages[i].emaPrice = getRandInt64();
|
|
|
+ priceFeedMessages[i].emaConf = getRandUint64();
|
|
|
+ }
|
|
|
+
|
|
|
+ (updateData, updateFee) = createWormholeMerkleUpdateData(
|
|
|
+ priceFeedMessages
|
|
|
+ );
|
|
|
+
|
|
|
+ // reparse
|
|
|
+ priceFeeds = pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
+
|
|
|
+ for (uint i = 0; i < priceFeeds.length; i++) {
|
|
|
+ assertParsedPriceFeedEqualsMessage(
|
|
|
+ priceFeeds[i],
|
|
|
+ priceFeedMessages[i],
|
|
|
+ priceIds[i]
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function testParsePriceFeedWithWormholeMerkleWorksRandomDistinctUpdatesInput(
|
|
|
+ uint seed
|
|
|
+ ) public {
|
|
|
+ setRandSeed(seed);
|
|
|
+
|
|
|
+ uint numPriceFeeds = (getRandUint() % 10) + 1;
|
|
|
+ PriceFeedMessage[]
|
|
|
+ memory priceFeedMessages = generateRandomPriceFeedMessage(
|
|
|
+ numPriceFeeds
|
|
|
+ );
|
|
|
+
|
|
|
+ (
|
|
|
+ bytes[] memory updateData,
|
|
|
+ uint updateFee
|
|
|
+ ) = createWormholeMerkleUpdateData(priceFeedMessages);
|
|
|
+ bytes32[] memory priceIds = new bytes32[](numPriceFeeds);
|
|
|
+ for (uint i = 0; i < numPriceFeeds; i++) {
|
|
|
+ priceIds[i] = priceFeedMessages[i].priceId;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Shuffle the priceFeedMessages
|
|
|
+ for (uint i = 1; i < numPriceFeeds; i++) {
|
|
|
+ uint swapWith = getRandUint() % (i + 1);
|
|
|
+ (priceFeedMessages[i], priceFeedMessages[swapWith]) = (
|
|
|
+ priceFeedMessages[swapWith],
|
|
|
+ priceFeedMessages[i]
|
|
|
+ );
|
|
|
+ (priceIds[i], priceIds[swapWith]) = (
|
|
|
+ priceIds[swapWith],
|
|
|
+ priceIds[i]
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Select only first numSelectedPriceFeeds. numSelectedPriceFeeds will be in [0, numPriceFeeds]
|
|
|
+ uint numSelectedPriceFeeds = getRandUint() % (numPriceFeeds + 1);
|
|
|
+
|
|
|
+ PriceFeedMessage[]
|
|
|
+ memory selectedPriceFeedsMessages = new PriceFeedMessage[](
|
|
|
+ numSelectedPriceFeeds
|
|
|
+ );
|
|
|
+ bytes32[] memory selectedPriceIds = new bytes32[](
|
|
|
+ numSelectedPriceFeeds
|
|
|
+ );
|
|
|
+
|
|
|
+ for (uint i = 0; i < numSelectedPriceFeeds; i++) {
|
|
|
+ selectedPriceFeedsMessages[i] = priceFeedMessages[i];
|
|
|
+ selectedPriceIds[i] = priceIds[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ PythStructs.PriceFeed[] memory priceFeeds = pyth.parsePriceFeedUpdates{
|
|
|
+ value: updateFee
|
|
|
+ }(updateData, selectedPriceIds, 0, MAX_UINT64);
|
|
|
+ for (uint i = 0; i < numSelectedPriceFeeds; i++) {
|
|
|
+ assertParsedPriceFeedEqualsMessage(
|
|
|
+ priceFeeds[i],
|
|
|
+ selectedPriceFeedsMessages[i],
|
|
|
+ selectedPriceIds[i]
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function testParsePriceFeedWithWormholeMerkleRevertsIfPriceIdNotIncluded()
|
|
|
+ public
|
|
|
+ {
|
|
|
+ PriceFeedMessage[] memory priceFeedMessages = new PriceFeedMessage[](1);
|
|
|
+ priceFeedMessages[0] = PriceFeedMessage({
|
|
|
+ priceId: bytes32(uint(1)),
|
|
|
+ price: getRandInt64(),
|
|
|
+ conf: getRandUint64(),
|
|
|
+ expo: getRandInt32(),
|
|
|
+ publishTime: getRandUint64(),
|
|
|
+ prevPublishTime: getRandUint64(),
|
|
|
+ emaPrice: getRandInt64(),
|
|
|
+ emaConf: getRandUint64()
|
|
|
+ });
|
|
|
+
|
|
|
+ (
|
|
|
+ bytes[] memory updateData,
|
|
|
+ uint updateFee
|
|
|
+ ) = createWormholeMerkleUpdateData(priceFeedMessages);
|
|
|
+ bytes32[] memory priceIds = new bytes32[](1);
|
|
|
+ priceIds[0] = bytes32(uint(2));
|
|
|
+
|
|
|
+ vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector);
|
|
|
+ pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ function testParsePriceFeedUpdateRevertsIfPricesOutOfTimeRange() public {
|
|
|
+ uint numPriceFeeds = (getRandUint() % 10) + 1;
|
|
|
+ PriceFeedMessage[]
|
|
|
+ memory priceFeedMessages = generateRandomPriceFeedMessage(
|
|
|
+ numPriceFeeds
|
|
|
+ );
|
|
|
+ for (uint i = 0; i < numPriceFeeds; i++) {
|
|
|
+ priceFeedMessages[i].publishTime = uint64(
|
|
|
+ 100 + (getRandUint() % 101)
|
|
|
+ ); // All between [100, 200]
|
|
|
+ }
|
|
|
+
|
|
|
+ (
|
|
|
+ bytes[] memory updateData,
|
|
|
+ uint updateFee
|
|
|
+ ) = createWormholeMerkleUpdateData(priceFeedMessages);
|
|
|
+
|
|
|
+ bytes32[] memory priceIds = new bytes32[](numPriceFeeds);
|
|
|
+ for (uint i = 0; i < numPriceFeeds; i++) {
|
|
|
+ priceIds[i] = priceFeedMessages[i].priceId;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Request for parse within the given time range should work
|
|
|
+ pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 100,
|
|
|
+ 200
|
|
|
+ );
|
|
|
+
|
|
|
+ // Request for parse after the time range should revert.
|
|
|
+ vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector);
|
|
|
+ pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 300,
|
|
|
+ MAX_UINT64
|
|
|
+ );
|
|
|
+
|
|
|
+ // Request for parse before the time range should revert.
|
|
|
+ vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector);
|
|
|
+ pyth.parsePriceFeedUpdates{value: updateFee}(
|
|
|
+ updateData,
|
|
|
+ priceIds,
|
|
|
+ 0,
|
|
|
+ 99
|
|
|
+ );
|
|
|
}
|
|
|
+
|
|
|
+ //TODO: add some tests of forward compatibility.
|
|
|
+ // I.e., create a message where each part that can be expanded in size is expanded and make sure that parsing still works
|
|
|
}
|